Macro expr becomes a tt?

How does this work? I would expect the second rule to not match since 1 + 1 contains multiple tokens. How does a expr with multiple tokens become one tt?

I want to understand this in order to write TT muncher macros. If a tt can represent any expr, that's nice, but I don't know if I should rely on this behavior.

macro_rules! f {
    ($e:expr) => (f!(@ $e));
    (@ $t:tt) => ($t);
}
let n = f!(1 + 1);
println!("{}", n); // prints "2"

I think it works because $e is one token -- I guess that expansion happens later. Or maybe the expr acts like it has parentheses? For example, f!(@ $e + 1) fails, but f!(@ ($e + 1)) works too.

Here's the trace_macros! output. It doesn't really help.

    = note: expanding `f! { 1 + 1 }`
    = note: to `f ! (@ 1 + 1)`
    = note: expanding `f! { @ 1 + 1 }`
    = note: to `1 + 1`

There is a rule that a macro can parse everything only once using parsing rules like expr or pat, etc. This means that an 1+1 expr, after having been assigned to an $e:expr metavariable once cannot be split up into 3 tokens again. In particular you also, AFAIK, cannot try to parse tokens into something different after parsing them as expr failed. Edit: Little test reveals that it is more like: you cannot try to parse a sequence of tokens into something different after parsing them as expr failed and that parsing attempt already consumed at least one token.

Also, in a sense this expr parsing does give it parantheses-like wrapping, for example you won’t make any mistake by putting an $e:expr into an expression like $e * 5, a huge footgun in C macros if you forget parantheses. I suspect that trace_macros might give confusing/misleading outputs, too, if you do something like $e * 5 with $e being 1+1.

My best guess is that the reason that tt still accepts an already-parsed expression is that you’d still want to be able to use $($token:tt)* as a way to say I don’t care, I’ll accept anything here.

5 Likes

I feel more confident writing complex macros with that understanding!

Would that be any designator other than tt?

Good to know!

Sure enough. In my test, the output showed 1 + 1 * 5 where the runtime value is 10.

My understanding is that tt matches "anything". You seem to be interpreting it as a "single token" rather than a "token tree".

1 Like

Well, ident is not really creating any problems. Neither in parsing, since it’s only a single token, so it succeeds of fails without consuming anything, nor in restricted usability after matching, since it can be used pretty much anywhere and you wouldn’t have been able to split it up into more than one tt to begin with. I’d think of it ident more like a subset of tt.

I’m not sure about literal, I guess for every other rule every caveats apply fully.

A token tree must have a single root.

macro_rules! f {
    ($t:tt) => ($t);
}
f!(1 + 1); // does not compile
f!((1 + 1)); // ok
1 Like

Nevermind, I forgot lifetime, probably that one’s similar to ident. (Haven’t tested any of this).

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.