Unlike macro_rules!
, procedural macro would not recognize $crate
, I think the way to express crate path is using full crate name instead of $crate
, like ::my_crate::xxx
. But in some cases, like the macro being re-exported by other crate, obviously will break the macro. How to solve it? Thanks.
$crate
refers to crate name defining the decl macro. The equivalent of $crate
in proc macro is simply the absolute crate name path.
If you write the absolute path, the obvious error the caller may run into is the crate is not in the caller's scope. When the macro is reexported in another crate, no matter for the decl or proc macro, you won't solve the problem until the crate is usable in scope.
Got it. But another question, a procedural macro cannot be used in the crate(or the crate first export it) where it itself is, since the absolute path is not recognized. It must use the keyword crate
instead of using the crate name. Is that designed to be?
I don't understand the description.
For any macro, Rust checks the validity (of syntax, paths, or whatever) after expansion, i.e. on the caller site, not in the macro itself.
Like for the following code, it compiles
macro_rules! a {
() => (::_______________a_crate_name_that_never_exists______);
}
I'm sorry but I didn't mean that. I mean, for example, assume that my crate's name is my_crate
, I defined a macro like this:
pub fn func_a() {}
#[macro_export]
macro_rules! call_a {
() => {
::my_crate::func_a()
};
}
fn main() {
call_a!(); // Error!
}
Calling this function is ok in crates outside. But if in my_crate
, where ths macro itself is, will doesn't work. Because the compiler doesn't resolve path ::my_crate
. The compiler will says cannot find crate ``my_crate`` in the list of imported crates
It's because the crate name my_crate
used in your bin crate refers to the lib crate. If you move the call_a
macro and func_a
to the src/lib.rs
, it'll work. You can't use APIs from bin crates.
I'm afraid not.
Compiling hello v0.1.0 (D:\FancyFlame\hello)
error[E0433]: failed to resolve: could not find `hello` in the list of imported crates
--> src\lib.rs:6:11
|
6 | ::hello::func_a()
| ^^^^^ could not find `hello` in the list of imported crates
...
11 | call_a!(); // Error!
| --------- in this macro invocation
|
= note: this error originates in the macro `call_a` (in Nightly builds, run with -Z macro-backtrace for more info)
For more information about this error, try `rustc --explain E0433`.
error: could not compile `hello` due to previous error
Accroding to the error, I think $crate
in decl macro does two things:
- in the defining crate, it expands to the
crate
path prefix, so you can expand the macro in the defining crate - in the outer crate, it expands to the name of defining crate, thus you can call the macro there
So the question is which expansion does your proc macro intend to achieve?
Update:
- use
quote! { crate:: }
if you intend to refer to the caller's crate - or use absolute crate path
quote! { ::some_crate }
if you intend to refer to the dep crate
Yes, that is what I think. So I don't know how to realize features like $crate
in procedural macros.
Actually, it is a derive macro, but the same as a procedural macro. It is used both inside and outside crate. Just like this:
// my_crate/src/lib.rs
pub use my_crate_macros::MyTrait;
#[derive(Trait)]
pub struct MyStruct {
...
}
pub trait Trait {}
// my_crate_macros/src/lib.rs
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Trait)]
pub fn derive_trait(input: TokenStream) -> TokenStream {
let DeriveInput {
ident, generics, ..
} = parse_macro_input!(input as DeriveInput);
let (impl_gen, type_gen, where_clause) = generics.split_for_impl();
quote! {
impl #impl_gen ::my_crate::Trait for #ident #type_gen
#where_clause
{}
}
.into()
}
I don't know what could replace ::my_crate
.
Update:
Unfortunately, neither. I refer to the crate I want to export, like the way serde
exports its derive macros.
I don't think #[derive(Serialize)]
is used in serde crate defining the Serialize
trait. So the macro just needs to write the absolute path. If I'm wrong, feel free to correct me.
A simple way is to write impl #impl_gen Trait for #ident #type_gen
, the downside is everytime the caller must import Trait to scope. I've seen some custom derive macros use this.
Another way is: you could expose two separate macros, one for users and reexport it, the other for yourself and don't reexport it.
The third way is to leave some tokens that only serves you. When parsing, the additonal tokens used in your defining crate leads to the crate::
expansion, otherwise, absolute path tokens are expanded.
Update:
The recommened way: write impl #impl_gen ::my_crate::Trait for #ident #type_gen
, and in the root of crate defining Trait
, i.e. my_crate/src/lib.rs
, write extern crate self as my_crate;
. Now your macro works for all cases.
You don't. Since you simply can't use a procedural macro in the same crate that defines it, there's no point in even trying to express that.
Thanks for your workaround, it seems like there is no better way to realize it.
I didn't mean to use a procedural macro in the crate where it is, which marked proc-macro = true
. They are two crates, one containing derive macros that deriving from trait defining in that crate which is not marked proc-macro = true
. And that crate exporting those derive macros and other items. Should I not apply derive macros to items defined in that not-proc-macro crate?
You should. This is a very common pattern, see i.e. serde
and serde-derive
. But instead of $crate
, which is meaningless in your procedural macro, use the name of the crate that exposes your traits directly. Say you have my-crate
(containing the traits you want to derive) and my-crate-derive
(containing the derive macros), use my_crate::MyTrait
in your procedural macros.
If you are worried about people aliasing my-crate
, you can add an attribute where people can override the location of my-crate
, see serde's #[serde(crate = "...")]
attribute. But I wouldn't worry too much about this, unless someone raises an issue in your repository requesting this feature.
In that case, you would just write out the full path literally. If you have a crate foo
and foo-macros
, and foo-macros
generates code that refers to traits in foo
, then the generated code should refer to those traits (and all other items) using their fully-qualified path, foo::module::Trait
, foo::helper_function()
, etc.
But the question is back to that mentioned before: if the macro writes impl my_crate::Trait for ...
while I'm implementing Trait
for a struct defined in my-crate
, the compiler doesn't know what my_crate
refers to. If I modify the macro to impl crate::Trait for ...
, then the outer crate cannot use it.
I think it'll be better if rustc recognize the crate name like my_crate
as crate
keyword. I can't think of anything wrong it will cause.
And I think I can add use crate as my_crate;
, so my_crate::Trait
will be recognized in my_crate
.
Ah, I see. Yes, a workaround like use crate as my_crate;
would work. Or you could use an attribute, like I suggested. For example:
use my_crate_derive::DeriveFoo;
#[derive(DeriveFoo)]
#[derive_foo(crate = "crate")]
struct Foo;
I assume this has something to do with extern crate
, which you had to call in Rust 2015 explicitly in order to import crates. Since your current crate is not really an external crate, there is no implicit extern crate self as my_crate;
in your prelude, I'd say. Therefore you can't refer to your current crate by its crate name per default and have to do something like use crate as my crate;
or extern crate self as my_crate;
in your crate's root module . extern crate
is still the way to import crates, but it is now done implicitly, see this answer:
Thanks for the extern crate
tip. Although it's common to see that with #[macro_use]
now, I never knew it can globally import a crate
Thank you for your description and workarounds. I didn't know there was an implicit prelude which related to extern crate
.
Thanks for your time as well.