rustrust-macrosrust-proc-macros

Creating a function with custom name in a proc_macro in Rust


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?


Solution

  • You'll need to create an Ident:

    use proc_macro2::Span;
    
    let fn_name = Ident::new(&format!("{}_helper", &struct_name), Span::call_site());
               // ^^^^^^^^^^^^                                  ^^^^^^^^^^^^^^^^^^^^