2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Phil Lord
  • 2,917
  • 1
  • 20
  • 31
  • 4
    Once you have matched something using a specific matcher (eg. `block`), it won't ever be re-parsed into something else. `block` matches a block statement, and nothing else, in particular, a `struct` body is not a block statement. [You might be interested into looking at `tt`](https://stackoverflow.com/questions/40302026/what-does-the-tt-metavariable-type-mean-in-rust-macros). – mcarton Sep 04 '18 at 07:21
  • Yes, you are right, tt does the job – Phil Lord Sep 04 '18 at 08:10
  • I have mostly given up on trying to match Rust constructs in `macro_rules!`. I think it's more trouble than it's worth. There are plenty of cases of valid Rust syntax which are actually impossible to match. – Peter Hall Sep 04 '18 at 12:12
  • @PeterHall I need to implement 50 structs, all with a lot of similarity. Macros are the only way to go. – Phil Lord Sep 04 '18 at 12:19
  • @PhilLord There are other approaches though. 1- You can invent your own syntax, which is easier to match, and generates the structs. 2- Use procedural macros. – Peter Hall Sep 04 '18 at 12:25
  • @PeterHall I don't want to invent syntax for the sake of it. Makes the code hard to read. As @mcarton says, `tt` works well anyway. – Phil Lord Sep 05 '18 at 07:22

1 Answers1

5

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,
    }
}
dtolnay
  • 9,621
  • 5
  • 41
  • 62