Macro referencing a struct from the same and other crates

I have a struct A and a macro test_factory that provides syntactic sugar for creating test instances of A:

mod a {
  pub struct A {}
  
  #[macro_export]
  macro_rules! test_factory { () -> crate::a::A{} }
}

#[cfg(test)]
mod b {
  #[test]
  fn test_something() {
    let my_a = crate::test_factory!();
    //...
  }
}

The project grew, and I am turning it into a workspace with several packages. I want the test_factory! macro to be usable for tests in all the workspace's packages, so I separated it out into a separate package, and add it to the other packages' dev-dependencies.
When I use the macro from the same package where A is defined, it works. If I use it from another package, the compiler fails saying that there is no crate::a::A, which is of course correct. If I change the macro so it references A by crate:

  macro_rules! test_factory { () -> crate_of_a::a::A{} }

then it is usable from other packages but not from the one defining A.
Any suggestions for resolving this?

This is a job for $crate, a special always defined substitution variable, that expands to the crate the macro is defined in.

An example for this use case is here towards the bottom of the Edition 2015 section..

But I moved to macro to a separate crate, so I can add it to dev-dependencies of other packages and have it available in their tests.
As I understand $crate, it refers to the crate the macro is defined in, and not the crate a struct like A is defined in. Am I missing something?

Ah, I misunderstood, sorry. If I am not again mistaken, one work-around would be to define either more macro match arms or multiple macros, and pass a path substitution variable around.

For example with multiple macro arms:

#[macro_export]
macro_rules! test_factory { 
   () -> { 
  	  test_factory!(__inner crate_of_a::a::A)
   };
   (local) -> { 
  	  test_factory!(__inner $crate::a::A)
   };
   (__inner $a_path: path) -> { 
  	  $a_path{}
   };
}

Or as multiple macros:

// In the shared dev-deps crate. Let's call it `shared`
#[macro_export]
macro_rules! test_factory_inner { 
   ($a_path: path) => {{
       $a_path{}
   }}
}

#[macro_export]
macro_rules! test_factory { 
   () => {{
       $crate::test_factory_inner!(crate_of_a::a::A)
   }}
}


// inside `crate_of_a`
#[macro_export]
macro_rules! test_factory { 
   () => { 
       shared::test_factory_inner!($crate::a::A)
   }
}

Edit: The previous version ran into Macros: limitation in the expression parser for <$:path>::<ident> · Issue #48067 · rust-lang/rust · GitHub so the whole path, including A needs to be passed. Here's a working proof of concept for the multiple macro version.

1 Like

Wow, thank you for the thorough reply, even with a sample repo. I really appreciate the time and effort you put into this, thank you!

1 Like

I found a solution that works even better in my context: I re-export A in the shared crate using pub use A, and then reference it in the macro using $crate.

Cool. That way does seem to be a lot simpler.

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.