问题
I am writing a procedural macro and I need to emit a very long identifier multiple times (potentially because of hygiene, for example). I use quote!
to create TokenStream
s, but I don't want to repeat the long identifier over and over again!
For example, I want to generate this code:
let very_long_ident_is_very_long_indeed = 3;
println!("{}", very_long_ident_is_very_long_indeed);
println!("twice: {}", very_long_ident_is_very_long_indeed + very_long_ident_is_very_long_indeed);
I know that I can create an Ident
and interpolate it into quote!
:
let my_ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
quote! {
let #my_ident = 3;
println!("{}", #my_ident);
println!("twice: {}", #my_ident + #my_ident);
}
So far so good, but I need to use that identifier in many functions all across my code base. I want it to be a const
that I can use everywhere. However, this fails:
const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
With this error:
error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
--> src/lib.rs:5:70
|
5 | const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
| ^^^^^^^^^^^^^^^^^
error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
--> src/lib.rs:5:20
|
5 | const FOO: Ident = Ident::new("very_long_ident_is_very_long_indeed", Span::call_site());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I doubt that those functions will be marked const
any time soon.
I could make the string itself a constant:
const IDENT: &str = "very_long_ident_is_very_long_indeed";
But then wherever I want to use the identifier, I need to call Ident::new(IDENT, Span::call_site())
, which would be pretty annoying. I want to just write #IDENT
in my quote!
invocation. Can I somehow make it work?
回答1:
Fortunately, there is a way!
The interpolation via #
in quote!
works via the ToTokens trait. Everything implementing that trait can be interpolated. So we just need to create a type that can be constructed into a constant and which implements ToTokens
. The trait uses types from proc-macro2
instead of the standard proc-macro
one.
use proc_macro2::{Ident, Span, TokenStream};
struct IdentHelper(&'static str);
impl quote::ToTokens for IdentHelper {
fn to_tokens(&self, tokens: &mut TokenStream) {
Ident::new(self.0, Span::call_site()).to_tokens(tokens)
}
}
Now you can define your identifier:
const IDENT: IdentHelper = IdentHelper("very_long_ident_is_very_long_indeed");
And directly use it in quote!
:
quote! {
let #IDENT = 3;
}
(Full example)
来源:https://stackoverflow.com/questions/59619244/how-can-i-store-an-identifier-proc-macroident-as-a-constant-to-avoid-repea