Dogbert
Dogbert

Reputation: 222108

Is it possible to implement this macro using only `macro_rules!`?

I'm trying to make a macro that would let me iterate through a list of types to reduce trait impl boilerplate. (I'm currently using a different macro based solution, but this seems more readable, if it's possible without adding a dependency.)

This is the syntax I am aiming for:

trait MyTrait {}

tfor! {
    for Ty in [i32, u32] {
        impl MyTrait for Ty {}
    }
}

My attempt:

macro_rules! tfor {
    (for $ty:ident in [$($typ:ident),*] $tt:tt) => {
        $(
            type $ty = $typ;
            tfor! { @extract $tt }
        )*
    };
    (@extract { $($tt:tt)* }) => {
        $($tt)*
    };
}

This generates an error as both the iterations define a type named Ty in the same scope:

   |
4  |               type $ty = $typ;
   |               ^^^^^^^^^^^^^^^^
   |               |
   |               `Ty` redefined here
   |               previous definition of the type `Ty` here

Playground

Is there a way around this? Can I somehow generate a random identifier in place of Ty so that this compiles, without using a proc macro or a dependency?

Upvotes: 2

Views: 554

Answers (1)

jonasbb
jonasbb

Reputation: 2583

You can scope the trait implementation inside a const declaration. That way you can re-use the Ty name without causing conflicts.

macro_rules! tfor {
    (for $ty:ident in [$($typ:ident),*] $tt:tt) => {
        $(
            const _: () = {
                type $ty = $typ;
                tfor! { @extract $tt }
            };
        )*
    };
    (@extract { $($tt:tt)* }) => {
        $($tt)*
    };
}

trait MyTrait {}

tfor! {
    for Ty in [i32, u32] {
        impl MyTrait for Ty {}
    }
}

Upvotes: 8

Related Questions