Confusing macro error

Considering the following code:

macro_rules! inner {
    ($(<$($ty:path : $bound:path),*> $struct:path),*) => {
        $(
            impl<$($ty : $bound),*> Blanket for $struct {}
        )*
    }
}
inner! {
    <T1: Bound, T2: Bound> Pair<T1, T2>
}

// how is it expected to work:
trait Bound {}
trait Blanket {}
struct Pair<T1, T2>(std::marker::PhantomData<(T1, T2)>)
// then, the macro will do
// impl<T1: Bound, T2: Bound> Blanket for Patr<T1, T2>

(Playground)

Errors:

error: expected one of `!`, `+`, `>`, or `as`, found `:`
  --> src/lib.rs:4:24
   |
4  |               impl<$($ty : $bound),*> Inner for $struct {}
   |                          ^ expected one of `!`, `+`, `>`, or `as` here
...
8  | / inner! {
9  | |     <T1: Bound, T2: Bound> Pair<T1, T2>
10 | | }
   | |_- in this macro invocation

The error simply doesn't make sense for me. What this could mean?

The grammar of Rust is quite strict, and things that look "alike" (ident, ty and path) are really different. Unless you use $($tt:tt)* to capture a raw stream of tokens, you will force these Tokens to be preparsed as one of these categories. In your case you have made the stream of tokens "T1" become a path.

And the Rust reference states that (type) generics must be of the form:

$(
    $T:ident $(
        : $(
            $( for<...> )?
            $trait:path
        )+* $(+)?
    )?
),* $(,)

So by changing your inner! macro to:

macro_rules! inner {(
    $(
        <$($ty:ident : $bound:path),* $(,)?> $struct:path
    ),* $(,)?
) => (
    $(
        impl<$($ty : $bound),*> Blanket for $struct {}
    )*
)}

it works.

1 Like