Phil Lord
Phil Lord

Reputation: 3067

Why can I not match a struct's body using the `block` matcher in a Rust macro?

I am trying to match a struct in a Rust macro. I need to pull the struct apart somewhat to get its name. I figured that the block matcher would do the trick. Consider, for example this:

macro_rules! multi {
    (struct $name:ident $block:block) => {
        enum George {$name}
    }
}

multi!{
    struct Fred {
        a:String
    }
}

which expands to

enum George { Fred, }

which is about right.

However, if I turn this back into a struct, it fails.

macro_rules! multi {
    (struct $name:ident $block:block) => {
        struct $name $block
    }
}

which gives this error.

 error: expected `where`, `{`, `(`, or `;` after struct name, found `{ a: String }`
   --> src/main.rs:64:22
    |
 64 |           struct $name $block
    |                        ^^^^^^ expected `where`, `{`, `(`, or `;` after struct name

It looks like {a: String} is being treated as a single token, rather than being re-parsed; but it is what should be going in there.

Upvotes: 2

Views: 2358

Answers (1)

dtolnay
dtolnay

Reputation: 11043

The $:block matcher is for block expressions, i.e. a set of curly braces containing zero or more Rust statements and items and an optional trailing return value. For example the following are blocks:

{
    let x = 1;
}
{
    #[derive(Default)]
    struct S;
    S::default()
}

Examples of curly braces in Rust that are blocks are:

  • function bodies,
  • if and else clauses,
  • loop bodies of for, while, and loop loops.

The curly braces around the fields of a struct are not a block because they are not supposed to contain zero or more Rust statements and items followed by an optional trailing return value. Instead they are supposed to contain the names and types of the struct fields.

In a macro you can match an arbitrary set of curly braces using the pattern { $($tt:tt)* }, which means "curly braces containing any number of arbitrary tokens."

macro_rules! multi {
    (struct $name:ident { $($tt:tt)* }) => {
        struct $name { $($tt)* }
    };
}

multi! {
    struct Fred {
        a: String,
    }
}

Upvotes: 5

Related Questions