Macro rule matching gives up too early?


#1

Is this a bug or am I doing something wrong?

macro_rules! add {
    ($x:expr) => { $x };
    ($x:expr,$y:expr) => { $x + $y }
}

macro_rules! one {
    () => {1};
}

macro_rules! two {
    () => {1,2};
}

fn main() {
    println!("{}", add!(one!()));
    println!("{}", add!(1,2));
    println!("{}", add!(two!()));
}

The last two lines should be equivalent and both print “3”. Alas, the compiler complains

   Compiling hello v0.1.0 (file:///Users/hannes/workspace/hello)
error: macro expansion ignores token `,` and any following
  --> src/main.rs:11:13
   |
11 |     () => {1,2};
   |             ^
   |
note: caused by the macro expansion here; the usage of `two!` is likely invalid in expression context
  --> src/main.rs:17:25
   |
17 |     println!("{}", add!(two!()));
   |                         ^^^^^^

error: aborting due to previous error

error: Could not compile `hello`.

To learn more, run the command again with --verbose.

Process finished with exit code 101

#2

The behavior is correct because the expression two!() is a valid expr. You can tell by making the following change:

- ($x:expr) => { $x };
+ ($x:expr) => { "x" };

which prints x 3 x.

The Little Book of Rust Macros gives some ways to write macros that delegate to other macros, for example via TT muncher.


#3

I see. There are two ways to reduce the expression add!(two!()): expand add! before two! or the other way around. The former leads to the above error since, as you point out, two()! is a token that matches expr. The latter would lead to success. Is there any disadvantage to the latter? It seems more intuitive and more powerful. If add and two were functions, it would be the only possible evaluation strategy.


#4

Thank you for the pointer to the Little Book of Rust Macros. It’s a very helpful resource.

I think an elegant solution to this issue with the compiler would be to add a special token type for expr minus macro invocations. That token type would trigger the expansion of all macros referenced in the arguments before an attempt is made at matching the arguments against the pattern in the macro definition.


#5

There is an RFC for that: