Ayush Garg
Ayush Garg

Reputation: 2517

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?

Upvotes: 3

Views: 933

Answers (1)

Chayim Friedman
Chayim Friedman

Reputation: 70840

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.

Upvotes: 7

Related Questions