Question regarding deriving re-exports

I'm running into issues using macros defined in other crates which pull in dependencies.

Consider the following crate which holds a macro, macro-crate:

pub use serde;

#[macro_export]
macro_rules! macro_a {
  ($from_ty:ident) => {
    #[derive(Debug, Clone, $crate::serde::Serialize, $crate::serde::Deserialize)]
    pub struct $from_ty(String);
  };
}

and the macro consuming crate, consumer-crate:

use macro_crate::macro_a;

macro_a!(SomeStruct);

fn main() {}

Building consumer-crate:

$ cargo build
...
error[E0463]: can't find crate for `serde`
 --> src/main.rs:3:1
  |
3 | macro_a!(SomeStruct);
  | ^^^^^^^^^^^^^^^^^^^^ can't find crate
  |
  = note: this error originates in the derive macro `$crate::serde::Serialize` which comes from the expansion of the macro `macro_a` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0463]: can't find crate for `serde`
 --> src/main.rs:3:1
  |
3 | macro_a!(SomeStruct);
  | ^^^^^^^^^^^^^^^^^^^^ can't find crate
  |
  = note: this error originates in the derive macro `$crate::serde::Deserialize` which comes from the expansion of the macro `macro_a` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0463`.
error: could not compile `consumer-crate` (bin "consumer-crate") due to 2 previous errors

I think this is an issue with the derive macro combined with the re-exported serde crate referenced from the $crate metavariable, but I can't figure out how to get this to work. Any ideas/tips/pointers?
I'd prefer the consumer-crate to not have to pull in serde (for reasons way more complex than the question) if at all possible.

I've tried a few other re-export solutions without luck.

Minimal reproducible example:

mkdir minimal-reproducible-example
cd minimal-reproducible-example
cargo new macro-crate --lib
cargo new consumer-crate --bin
echo 'serde = { version = "1.0.163", features = ["derive"] }' >> macro-crate/Cargo.toml
echo -e 'pub use serde;\n\n#[macro_export]\nmacro_rules! macro_a {\n  ($from_ty:ident) => {\n    #[derive(Debug, Clone, $crate::serde::Serialize, $crate::serde::Deserialize)]\n    pub struct $from_ty(String);\n  };\n}' > macro-crate/src/lib.rs
echo 'macro-crate = { path = "../macro-crate" }' >> consumer-crate/Cargo.toml
echo -e 'use macro_crate::macro_a;\n\nmacro_a!(SomeStruct);\n\nfn main() {}' > consumer-crate/src/main.rs
cd consumer-crate/
cargo build

Appreciate any help ^^

After a bit more testing, it looks like the macro_a macro is not required at all and the issue occurs whenever attempting to derive from a re-export:

use macro_crate::serde;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct A(String);

You need to tell serde_derive to use your re-exported serde. You should be able to achieve what you want with the #[serde(crate = "$crate::serde")] attribute, I believe.

1 Like

Thanks, I've gotten the following to work with serde:

use macro_crate::serde;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(crate = "crate_a::serde")]
pub struct A(String);

Though, I haven't been able to get it to work with $crate within a macro as it doesn't expand correctly (?):

error: failed to parse path: "$crate::serde"

As this is serde-specific, I assume there's no general solution to related problems? For example, I'm looking to use async_graphql::Scalar within a macro as well.

In theory, macro_a could accept a path for the serde crate and other dependencies but it's not ideal.

Too bad, I had hoped that it would work. $crate is a bit nicer than using the real crate name, as it could be changed with extern crate your_crate as whatever or in the manifest file.

Well, specifying an alternative import path via an argument to the macro is a common design choice. Tokio does the same, for example. But no, there is no universal way to override proc macros to change the path's they use in their generated code. Macros need to add support for that manually. Looks to me like Scalar doesn't support using a re-exported version of async_graphql via an argument.

1 Like

It makes sense, but I was hoping there was a nicer solution as this is really painful when you're nesting macros and mixing in optional features.

Appreciate the help!

1 Like