How does this macro optimize?

I have the following macro:

macro_rules! a {
    ($item:item) => {
        #[b]
        $item
    }
}

I am nesting it 10 times:

a!(a!(a!(a!(a!(a!(a!(a!(a!(a!(
    struct Foo;
))))))))));

How does it optimize? Is it like this:

#[proc_macro]
fn a(input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as Item);

   quote! { #[b] #item }
}

With 10 nested calls to a, the same macro input will be parsed and quoted 10 times in a row.

Or does the compiler optimize it to do all the work in 1 go:

#[proc_macro]
fn a(input: TokenStream) -> TokenStream {
    let item = parse_macro_input!(input as Item);

   quote! { #[b] #[b] #[b] #[b] #[b] #[b] #[b] #[b] #[b] #[b] #item }
}

The above is a very simplified example of the real use-case: I am making a crate derive_aliases and I am currently implementing it as a proc macro. I generate all the derives in a single go. I realized I can implement my crate using declarative macros instead, nesting the user's input in 10-20 plus calls to the macros. I have a feeling this will be much slower, but I'm wondering if the compiler has any optimization passes for macros which would make the declarative version faster

Just imagine this: another macro b!() nests the $item in 20 calls to a!(). b!() is called thousands of times in the code-base. These are the numbers

Or alternatively, b!() is a proc macro and does all of the work for 20 calls in a single pass. All logic for a!() is inlined in a single macro call. No nesting. $item is parsed just once.

I have a feeling the 2nd version will have much better compile-time speeds

I would expect the compiler to do the "unoptimized" version, although it may not fully reparse the input since AFAIK it may keep a partially parsed input around.

Your worries might be true, but my guess is that you'll need more than 20 items for the proc macro to become faster. Consider that calling a proc macro is much slower than a declarative macro since it requires compiling a separate library, dynamically load it and setup the proxy structures to pass to it. Moreover proc macros have less caching opportunities than declarative macros, and they are currently rerun even in incremental builds.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.