$crate metavariable for procedural macros?

When developing procedural macros, it's a common pattern that you need to access some accessory types or names from the related "runtime" crate. This is commonly done simply by assuming some fixed crate name. For example, in case of ref-cast, macros assumes that runtime crate will be ref_cast.

This works most of the time, however, it's not ideal (although, it's worth mentioning that 99% of the time it works exactly as designed):

  1. If runtime crate is renamed via Cargo.toml for whatever reason, code will stop compiling:
# This will break `#[derive(Fail)]` functionality!
success = { package = "failure", version = "0.1.6" }

For macro_rules, there is a $crate metavariable which always points to the defining crate.

  1. Sometimes, there is a need to hide somebody's else procedural macro behind your own abstractions.

For example, here inventory crate is used inside gflags crate; inventory crate needs ctor crate and to avoid forcing everyone to have an explicit dependency on ctor, inventory re-exports ctor from inventory itself. However, when inventory is used internally by gflags, the inventory is no longer a direct dependency of gflags users so ::inventory::ctor will no longer compile. To solve that, inventory supports a special attribute telling it which prefix to use.

However, most of the procedural macros don't support anything like that since it's not anticipated that they are going to be used from other crates or that their "runtime" crates might be renamed.

Question

The question I have is if it would be possible to define some magic $crate metavariable, but for procedural macroses? So, every time procedural macro needs its dependencies it can generate code like:

quote! {
    // We don't really know our "runtime" crate name, but that's okay!
    use $crate::internal::SomeImportantTrait;
}

Here is how I think it might work. Oftentimes, procedural macroses are re-exported from the "runtime" crate, like this:

pub use ref_cast_impl::RefCast;

What if there was an attribute that would allow to change what $crate would expand via an attribute on a use statement. Like this:

// This would make `$crate` generated by `ref_cast_impl::RefCast` to be replaced by `ref_cell`
#[crate = ref_cell]
pub use ref_cast_impl::RefCast;

Or like this:

// This would make `$crate` generated by `ref_cast_impl::RefCast` to be replaced by whatever name this crate has in code where `derive(RefCell)` is used.
#[crate = crate]
pub use ref_cast_impl::RefCast;

Or even like this:

// Now I'm re-exporting it again, and this time I'm making the target macro to produce `<this crate>::ref_cast` in place of `$crate`!
#[crate = crate::ref_cast]
pub use ref_cell::OurRefCast;

// Re-export all ref_cast runtime needs so `OurRefCast` can reference them.
#[doc(hidden)]
pub use ref_cast;

And the default behavior could be #[crate = crate], i.e, when you re-export procedural macro, it's $crate is "bound" to the crate doing the re-exporting.

Is it possible? Does it make any sense?

8 Likes

I really love this proposal! I was thinking of a similar thing recently.

I think part of the problem is that procedural macros are compiled for the host target, matching the compiler itself, which may be totally different than the target you will finally be building. So to re-export itself as $crate, we need cargo to know to also build this macro crate for that target, including all of its dependencies. Even when tying something else as $crate, the actual crate resolution would have to be deferred until the build of that final target.

I don't know anything about the compiler aside from basic principles that macros are compiled for the host target & loaded as dynamically loaded libraries, .

My thinking was that at the time of the expansion, compiler should know which export was matched by explicit use statement or fully-qualified use of the macro symbol (use serde::Deserialize; #[derive(Deserialize)] or #[derive(serde::Deserialize)]; so, it should be able to know attributes on that export to see where $crate should bind to.

Then, any tokens generated by macros invoked via that import could be post-processed and any $crate could be replaced by that path. Or $crate value could become a hidden argument to the macro invocation, so any invocation of macro imported through that import would get a path value (stream of tokens) $crate should be expanded to.

So I was assuming that this could be done without changing how macros is compiled, either by post-processing its tokens or by passing a hidden argument to the macro itself.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.