Search code examples
rustrust-macros

Dynamically creating parameters in nested rust macros


I've been tinkering around with Rust's macro system for a while now, and recently got interested in nesting two macros together, like this:

macro_rules! foo {
    () => {
        macro_rules! bar {
            () => {}
        }
    }
}

Relating to the example, I wanted to dynamically make parameter names in bar! which were passed into foo!, to obtain a result like this:

foo!(bar, baz); 

// The above call creates a macro, bar!, with the following definition:
macro_rules! bar {
    ( $bar:literal, $baz:literal ) => {
        println!("{}", stringify!( $bar, $baz ));
    }
}

To give a better idea of what I'm trying to do, here was my initial thought process on how this would work (this should parse exactly to the definition shown above):

macro_rules! foo {
    ( $( $attr:ident ), * ) => {
        macro_rules! bar {
            // the designator $$attr:literal consists of two parts - $attr,
            // which should be replaced with the arguments passed into foo!,
            // and $__:literal, which creates a literal designator for each of
            // the arguments from foo! for bar!
            ( $( $$attr:literal ), * ) => {
                // $( $$attr ), * follows the same logic as above
                println!("{}", stringify!( $( $$attr ), * ));
            }
        }
    }
}

This does look very weird, and sure enough, it didn't work, giving an error mentioning meta-variable expressions and this issue, both of which looked unrelated (full error can be seen on the playground).

Does anyone know if it is possible to dynamically create a macro with variables like this, and if so, how to do it?


Solution

  • Yes, however...

    You cannot insert the $ sign, as it is reserved for metavariables.

    You have two options to tackle that.

    On stable, you need to pass $ to the macro. Then it can refer to it using the metavariable.

    macro_rules! foo {
        ( $dollar:tt $( $attr:ident ), * ) => {
            macro_rules! bar {
                ( $( $dollar $attr:literal ), * ) => {
                    println!("{}", stringify!( $( $dollar $attr ), * ));
                }
            }
        }
    }
    
    foo!($ bar, baz);
    

    Playground.

    On nightly, you can escape the dollar sign: this is part of the feature macro_metavar_expr the compiler mentioned. You do it using $$:

    #![feature(macro_metavar_expr)]
    
    macro_rules! foo {
        ( $( $attr:ident ), * ) => {
            macro_rules! bar {
                ( $( $$ $attr:literal ), * ) => {
                    println!("{}", stringify!( $( $$ $attr ), * ));
                }
            }
        }
    }
    
    foo!(bar, baz);
    

    Playground.