Semver hazard around proc-macro/trait symbols ambiguity

The original problem that occurred to us was in strum crate update. The issue elaborates on the particular case and probably describes the problem in more detail as a good hands-on example.

But, I'll draw the problem in more generic words here.

At the time of this writing, rustc allows overloading the meaning of symbols if they have the same name in both type and macro namespaces.

This leads to a very hard-to-catch problem, and a caveat each developer should know about.
Suppose a crate mylib exposes both a trait named Foo and a proc-macro-derive name Foo for that trait.

There has been a tendency in the ecosystem that bare proc-macro crates should be private and their macros should be reexported from the library crate, optionally gated by a cargo feature e.g. cfg(feature = "derive").

However, this is restricted neither by the language, nor by cargo, and it is probably not encouraged by the documentation (but I may be wrong, if it is elaborated somewhere in Rust docs, please share a link). So, the crates like strum may not be reexporting the proc macros from their library crate, asking the users to depend on the proc-macro crate directly.

Having this code in upstream crates then leads to a server hazard:

use mylib::Foo;
use mylib_macros::Foo;

Because once mylib author decides to add pub use mylib_macros::Foo; to their mylib crate, they will probably release this change with a compatible minor semver bump. The developer expects the addition of a new symbol to reexports not to break any existing code because they think it's just an API extension.

However, this leads to breakage, because the meaning of use mylib::Foo; now becomes overloaded and it starts conflicting with use mylib_macros::Foo;.

This problem is very implicit and probably under-discussed. I don't know how we can solve this problem: either add language-level change to resolve that ambiguity if both paths point to the same symbol or somehow teach every Rust developer of this semver-hazard caveat.

I am really eager to hear the feedback from the Rust team.
Maybe this isn't really a frequent problem, it was just a headache to debug for us, so we are reporting it as feedback.

You might want to post this on internals.rust-lang.org or Zulip because that's where the compiler team tends to hang out.

From the user experience side of things I agree that this is a bit of a footgun. The main time I'll (ab)use this name overloading is with structopt.

use structopt::StructOpt;

#[derive(StructOpt)] // uses the macro
struct Args {
  input: String,
}

fn main() {
  let Args { input } = Args::from_args(); // uses the trait method
  ...
}

I'll try to use a fully qualified name everywhere else (e.g. #[derive(serde::Serialize)]), but that actually runs into the same problem because I'll use the macro via serde instead of serde_derive.

I think we might need a cultural change instead. It's too late for serde because of backwards compatibility, but what if people re-exported macros under something like structopt::macros::StructOpt?

To be clear, I do think that reexporting the proc macros from the library crate is the clean usage of this.
It is more convenient to have a single symbol imported and have it overloaded in proc-macro/trait namespaces. The semver hazard only concerns code where users depend on proc macro crate directly and they also import the trait symbol within the same scope.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.