I'm trying to learn more about procedural macros in Rust by creating a custom derive proc_macro. Here is a snippet of code I'm trying to make work using a function whose name is defined using a Rust variable inferred from the struct name. Here is the code for my macro:
use proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, parse_macro_input};
#[proc_macro_derive(MyTest)]
pub fn derive_mytest_fn(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let struct_name = &ast.ident;
let fn_name = format!("{}_helper", &struct_name);
let result = quote! {
fn #fn_name() {
println!("I'm helping out");
}
};
result.into()
}
and my main.rs
:
use my_test::{MyTest};
#[derive(MyTest)]
struct Test {
field1: u32,
}
fn main() {
Test_helper();
}
When running cargo run
, I end up with the error:
error: expected identifier, found `"Test_helper"`
--> src/main.rs:3:10
|
3 | #[derive(MyTest)]
| ^^^^^^
| |
| expected identifier
| in this derive macro expansion
|
::: <local path omitted>
|
6 | pub fn derive_mytest_fn(input: TokenStream) -> TokenStream {
| ---------------------------------------------------------- in this expansion of `#[derive(MyTest)]`
error: proc-macro derive produced unparsable tokens
--> src/main.rs:3:10
|
3 | #[derive(MyTest)]
| ^^^^^^
error[E0425]: cannot find function, tuple struct or tuple variant `Test_helper` in this scope
--> src/main.rs:9:5
|
9 | Test_helper();
| ^^^^^^^^^^^ not found in this scope
For more information about this error, try `rustc --explain E0425`.
I can't seem to get the #fn_name
variable to resolve correctly as the name of a function. I've tried various strategies like wrapping it in a separate quote!
like let fn_tok = quote!{ #fn_name }
and inserting that into the latter quote, but it keeps complaining that the identifier has quotes around it. What's the typical way to insert strings defined by Rust variables into a TokenStream when writing a macro like this, especially when you want to take a variable label and concat it with other labels/literals?
You'll need to create an Ident
:
use proc_macro2::Span;
let fn_name = Ident::new(&format!("{}_helper", &struct_name), Span::call_site());
// ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^