The serde_derive re-exports in Serde were added a long time ago for a totally different reason unrelated to 2018-style macro imports. It is a workaround for a limitation of Cargo cfgs. To the extent that they provide better import UX in 2018 crates, that is a fortunate coincidence, but let’s not see it as Serde’s solution to derive import UX just yet.
We have a thread in serde-rs/serde#1441 discussing how to proceed.
One observation from that thread is that a major drawback of a derive cfg is that it is non-additive when combined with 2018-style macro imports, and we have seen this cause real breakage. Consider a crate that compiles fine without the derive feature enabled:
use serde::Serialize;
use serde_derive::Serialize;
#[derive(Serialize)]
struct S;
fn f<T: Serialize>(...) {}
If some unrelated crate somewhere in your dependency tree decides to enable the derive cfg, the code here no longer compiles.
error[E0252]: the name `Serialize` is defined multiple times
--> src/main.rs:2:5
|
1 | use serde::Serialize;
| ---------------- previous import of the macro `Serialize` here
2 | use serde_derive::Serialize;
| ^^^^^^^^^^^^^^^^^^^^^^^ `Serialize` reimported here
|
= note: `Serialize` must be defined only once in the macro namespace of this module
help: you can use `as` to change the binding name of the import
|
2 | use serde_derive::Serialize as OtherSerialize;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I would like to see this fixed in the compiler before declaring derive cfg the recommended pattern. Both imports refer to the exact same macro so it may be fine for them not to conflict each other.
I think this would be nice but is not currently how things work. For example in the context of Serde, the Serialize trait is exported at serde::ser::Serialize and re-exported from the crate root as serde::Serialize. They are the same trait but you only get the derive macro if you import the latter way, not serde::ser::Serialize. We also can’t fix it because macros are limited to being exported from crate root.
I would be excited to see someone flesh out a design in an RFC by which trait definitions could couple themselves with a preferred derive macro for that trait. Basic straw man:
#[cfg_attr(feature = "derive", derive = "serde_derive::Serialize")]
pub trait Serialize {
/* ... */
}
This way any time you have trait Serialize in scope (whether from serde::Serialize or serde::ser::Serialize or any other path) and the derive cfg enabled, you have the derive macro in scope as well. This would fully realize the model that @withoutboats described: derive macros “seem like they are manifested by the trait itself.”