We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 4 of 28
Preserve expression grouping in a macro
Fix the transcriber of double_tt_fixed! so it doubles the value of the
whole input expression. Keep its matcher exactly ($($t:tt)*) and leave
double!, double_tt! and compare unchanged.
The supplied compare(a, b) deliberately shows two different results:
| Macro call | Arithmetic for this input |
|---|---|
double!(a + b) |
(a + b) * 2 |
double_tt!(a + b) |
a + (b * 2) |
For a = 1 and b = 2, compare returns (6, 5). The first value is the
intended doubling operation; the second demonstrates the precedence problem.
Your repair belongs in the third macro, double_tt_fixed!.
Required behavior
The wrapper calls the fixed macro with three unparenthesized expressions:
| Output field | Required value |
|---|---|
expr |
(a + b) * 2, from the unchanged double! |
tt |
a + (b * 2), from the unchanged double_tt! |
fixed_sum |
(a + b) * 2 |
fixed_diff |
(a - b) * 2 |
fixed_mixed |
(a * b + 1) * 2 |
Inputs are i64 values for which every intermediate operation in these
expressions fits in i64. No wrapping or saturating arithmetic is required.
For example, a = 3, b = 4 gives expr = 14, tt = 11, fixed_sum = 14,
fixed_diff = -2 and fixed_mixed = 26.
Do not compensate by changing a call site or by special-casing the test values. The macro should group the expression itself, so callers do not need extra parentheses. Keep the token-tree matcher; change how its captured tokens are placed into the output expression.
Why expr preserves grouping
$e:expr captures a parsed expression. Substituting it into $e * 2 preserves
that expression as a unit, so an input of a + b behaves as if it were
parenthesized before multiplication. Expansion produces an expression, not its
numeric answer; the resulting expression evaluates to 6 when a = 1, b = 2.
By contrast, $($t:tt)* captures a sequence of token trees. For a + b, the
sequence is a, +, b. Substituting that sequence before * 2 produces
a + b * 2. Multiplication takes precedence, so the result is 5 for those inputs.
A single token tree can also be a delimited group, such as (a + b). Thus tt
does not inherently lose grouping: the problem is this macro's ungrouped
repetition. Add grouping around the complete captured sequence in the
transcriber.
Choose a fragment for the syntax you accept
Use expr when you want Rust to parse an expression for you. Use token trees
when your macro needs to work with custom syntax or forward tokens without
first parsing them as an expression. Many useful macros combine fragment kinds;
complex macros do not all require token-tree parsing.
A captured expr is opaque when forwarded to another macro_rules! matcher.
That matcher can accept it as an expression, but cannot match the original
expression against literal tokens. tt captures can still be matched that way.
This complete example demonstrates the difference:
macro_rules! classify {
(3) => { "literal three" };
($other:expr) => { "expression" };
}
macro_rules! forward_expr {
($value:expr) => { classify!($value) };
}
macro_rules! forward_tt {
($value:tt) => { classify!($value) };
}
fn main() {
assert_eq!(forward_expr!(3), "expression");
assert_eq!(forward_tt!(3), "literal three");
}
Fragment choices also constrain what may follow a capture in a matcher.
For example, $a:expr + $b:expr is rejected: + is not an allowed follower
of an expr capture. A comma-separated interface can use expr captures;
an infix interface may need delimited operands or token-tree parsing. These
restrictions apply to the matcher, not to writing $e * 2 in the transcriber.
Check behavior as well as expansion text
Both original arithmetic macros compile for the supplied calls. The compiler checks that their expressions are valid Rust, but it cannot infer which arithmetic you intended. Tests that exercise different operators expose the mistake; a zero-only test can miss it.
Expansion viewers are useful, but printed expansion text is not a guaranteed
round trip back to the same program. Internal grouping may not be preserved
in the text, and different tools can print it differently. Do not assume every
viewer prints the two macros identically, or that copying its output preserves
all macro behavior. stringify! displays token text too; it does not prove
expression grouping or evaluate arithmetic.
Your solution must compile, match the outputs and pass Clippy with warnings treated as errors.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.