Use of imported types in derive macro

Say I want to implement a derive macro for MyTrait:

trait MyTrait {
  fn get(&self) -> MyType;
}

struct MyType;

What is the proper way to use the MyTrait and MyType symbols inside a derive macro? One solution I found is to write the full path to the type:

// ...

quote! {
  impl ::my_crate::MyTrait for #ident {
    fn get(&self) -> ::my_crate::MyType {
      // ...
    }
  }
}

//...

However, this is not as robust as I would like, since it assumes that the user didn't rename the crate my_crate to something else in their Cargo.toml. Also, I can't use this macro in tests within my_crate, since I should instead write ::crate::{MyTrait, MyType} in this situation.

Another solution is to write only MyTrait and MyType, but then the users need to import all the types that MyTrait uses (which in my case is a lot), so this isn't acceptable either.

Is there a clean way to do this?

I like how tokio solves this with an attribute: main in tokio - Rust. But my honest opinion would be not to worry too much about it and only add it once someone raises an issue requesting such a feature.

1 Like

There's unfortunately no perfect solution yet. The typical solution is to hardcode absolute paths that assume the runtime crate is a direct dependency and not renamed, as well as offer an optional helper attribute that specifies a non-default path to the crate.

For function like proc macros specifically, you can make a macro_rules! wrapper which provides a $crate token, but there's no such way to wrap derive or attribute macros.

For this there's a niche feature that you can write extern crate self as my_crate, which will allow you to use ::my_crate in addition to ::crate.

5 Likes

Thank you to both of you; I wish I could set both as "Solution", since I used both of your answers as part of my current implementation!

I ended up

  • not worrying about crate renaming for now - I can always use the tokio trick if ever someone requests it
  • combine the extern crate self as my_crate trick with further inspiration from serde

I added an extra helper attribute to my derive macro called #[mock] (undocumented in the public docs of my library). I use it to indicate to my macro that it is being used in the context of my crate. Then, I either generate

// When not in my crate
const (): _ = {
  extern crate my_crate as _my_crate;

  // .. my stuff, with all paths starting with _my_crate
};

// When in my crate (#[mock] is present)
const (): _ = {
  extern crate self as _my_crate;

  // .. my stuff, with all paths starting with _my_crate
};

I got this idea from serde. The advantage here is that my whole implementation simply uses paths that start with _my_crate::, without needing to care if we're in my crate or not.

A perhaps simpler alternative would be just add extern crate self as my_crate (and use my_crate:: paths everywhere) - which I believe is what @CAD97 was suggesting - but my guess is that dtolnay saw that this created some name clashes sometimes, and hence preferred to create a new symbol (without polluting the global namespace)? In any case, it works now, thank you!

serde's macros were written in the 2015 edition, when extern crate was required and ::name just refered to a name at the root of the current crate.

1 Like

just want to add, there's already effort to solve this problem, but not a definite conclusion yet. there's proposal to use cargo (with an added section for the Cargo.toml manifest such as [runtime-dependencies] or [macro-dependencies]) to resolve and unify the crate, there's also proposal to add hygiene context in proc macros similar to declarative macros. you can check the discussion here

1 Like

Got it. I removed the extra const (): _ stuff, and now I simply add extern crate self as my_crate when the macro is used within my_crate, as you suggested.

Thanks!

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.