Strange rust macro behavior with delimiters

Given this macro:

macro_rules! mac {
	([$p:path] $body:tt) => {
	    ($p $body)
	};
}

fn main() {
    mac!([foo::bar] { foo: "bar" });
}

For some reason it expands into this:

fn main() { (foo::bar, { (/*ERROR*/) }); }

What I expected:

fn main() { (foo::bar { foo: "bar" }); }

I have no idea why rust adds a comma there when there isn't a single one in my program. Is it assuming I'm trying to create a tuple because of the parenthesis and automatically inserting the comma for me? I find it strange that something as abstract as macros would do that.

If we change the parenthesis to brackets like so:

macro_rules! mac {
	([$p:path] $body:tt) => {
	    [$p $body]
	};
}

I get this error instead:

error: expected one of `,`, `.`, `;`, `?`, `]`, or an operator, found `{`
 --> src/main.rs:8:21
  |
8 |     mac!([foo::bar] { foo: "bar" });
  |                     ^ expected one of `,`, `.`, `;`, `?`, `]`, or an operator

What I expected:

fn main() { [foo::bar { foo: "bar" }]; }

The error isn't very clear but I'm guessing its somewhat related to the previous compile issue.

Finally, if we remove delimiters altogether:

macro_rules! mac {
	([$p:path] $body:tt) => {
	    $p $body
	};
}

I get the expected result:

fn main() { foo::bar { foo: "bar" }; }

I can think of a few ways to get around this with tt-munching but I don't quite understand why these macros error in the first place. Is this behavior a bug or intentional design?

playground

Parenthesis in Rust are called grouped expressions. Here a quote from the documentation with emphasis added by me:

A parenthesized expression wraps a single expression, evaluating to that expression.

What you are trying to pass to the grouped expression is seemingly not evaluated as a single expression by the compiler. I actually have no idea how $p $body is interpreted by the compiler. But it is not interpreted as a struct expression, because writing the literal (foo::bar{ foo: "bar"}), without the macro invocation spliting the path from the body obviously works.

Wrapping $p $body in a block expression inside the grouped expression works as well:

macro_rules! mac {
	([$p:path] $body:tt) => {
	    ({$p $body})
	};
}

Playground.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.