[Solved] Problem with variable names in a macro

I have a enum where each variant contains some data and some metadata. The metadata is always of a different type, but they all share a common trait. I'd like to lift this trait to the enum type.

Because I don't want to write the same code over and over again (the trait has a lot of methods), here is a macro that helps me:

…

( $name:ident : $t1:ty ) => {
    fn $name(self, arg1: $t1) -> Self {
        match self {
            Graph::XY(data, meta) => Graph::XY(data, meta.$name(arg1)),
            Graph::Point(data, meta) => Graph::Point(data, meta.$name(arg1)),
        }
    }
};

( $name:ident : $t1:ty, $t2:ty ) => { ... }
…

All of the trait methods take ownership of self, some data and return ownership. This is some kind of builder pattern I guess.

I would like to avoid having all these different macro rules for different numbers of parameters. Idealy, there should only be

( $name:ident : $($t:ty),* ) => { ??? }

but I fail at unrolling that into a parameter list

fn $name(self, (???: $t)*) { 
    … meta.$name( (???)* ) …
}
  • Can anyone help me with that?
  • Am I maybe on the wrong track and can do this without any macro use?

Could you give us more details on what kinds of parameters you expect to find into the macro? Like an example of what the code you want to convert into a macro looks like? It sounds like you'd want something like a recursive macro here though

Creating a new symbol out of nowhere is called a gensym and still in the discussion. However my gut feeling is that you can easily work around the lack of gensym by having a fixed and long enough list of unique symbols. This is done in two phases, the first is to interleave $ts with predefined list of names:

macro_rules! interleave_and_call {
    ($next:ident! [$($acc:tt)*] [] $_nn:tt) => (
        $next!($($acc)*)
    );
    ($next:ident! [$($acc:tt)*] [$t:ty, $($tt:tt)*] [$n:ident, $($nn:tt)*]) => (
        interleave_and_call!($next! [$($acc)* ($n: $t)] [$($tt)*] [$($nn)*])
    );
}

This macro will translate interleave_and_call!(foo! [whatever here] [t1, t2,] [n1, n2, n3, ...]) into foo!(whatever here (t1: n1) (t2: n2)). Note that n3 and others are ignored. Also note that this is a recursive macro so it cannot handle too many arguments, but practically this should not be a problem unless you are using it as a part of complex external macros.

Now we can make the actual function (adapting to your code should be simple) with this macro:

macro_rules! make_fn {
    (@internal $name:ident : $(($n:ident: $t:ty))*) => (
        fn $name($($n: $t),*) {
            println!("{:?}", ($($n),*));
        }
    );
    ($name:ident : $($t:ty),*) => (
        // here we use 8 names, but it is up to you to supply more
        interleave_and_call!(make_fn! [@internal $name :] [$($t,)*] [a, b, c, d, e, f, g, h])
    );
}

fn main() {
    make_fn!(foo : i32, String);
    foo(42, "hello".into());
}

(I recommend to read The Little Book of Rust Macros for this level of macro usages.)

1 Like

Thanks to macro hygiene, any literal identifier is a gensym.

macro_rules! interleave_and_call {
    ($next:ident! [$($acc:tt)*] []) => (
        $next!{$($acc)*}
    );
    ($next:ident! [$($acc:tt)*] [$t:ty, $($tt:tt)*]) => (
        // Declare an unbounded number of variables uniquely named "x".
        interleave_and_call!{$next! [$($acc)* (x: $t)] [$($tt)*]}
    );
}

macro_rules! make_fn {
    (@internal $name:ident : $(($n:ident: $t:ty))*) => (
        fn $name($($n: $t),*) {
            println!("{:?}", ($($n),*));
        }
    );
    ($name:ident : $($t:ty),*) => (
        // here we use 0 names :P
        interleave_and_call!{make_fn! [@internal $name :] [$($t,)*]}
    );
}

fn main() {
    make_fn!(foo : i32, String);
    foo(42, "hello".into());
}

Though, granted, foo(x: i32, x: String) probably doesn't make the best signature for anything that is going to show up in rustdoc.

2 Likes

@lifthrasiir, @ExpHP: Thanks, both of you. It works great now!