3

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?

1 Answer 1

7

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you so much! I thought this feature was a bit too over-the-top to be possible, but Rust never disappoints :) After some more digging, it looks like the nightly feature is (very!) active in development and is coming as soon as 1.62.0! Also, you might want to change your link for macro_metavar_expr to this - it seems to have the proper information.
@AyushGarg Oh yeah, I sometimes link to the unstable book or to the RFCs book or to the RFC document, but I didn't see the RFC document hasn't been updated to include the tracking issue & RFC PR this time :(

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.