Rust macros are smarter than just text substitution
This is a safety feature that prevents macros expanding in an unexpected way.
The footgun in C macros
C macros are literal text substitution by the preprocessor. The macro expression is replaced in your source code before it’s compiled, which creates a footgun. You can get unexpected results if the expression with substitutions doesn’t have the operator precedence you expect.
Here’s a trivial example of a C macro that gets this wrong:
#define ADD_ONE(X) X + 1
ADD_ONE(9) * 2
What’s the result?
A human might read ADD_ONE(9)
like a function call, so expect it to be evaluated before any other operators. They’d thus expect the result::
ADD_ONE(9) * 2 = 10 * 2 = 20
But the C compiler just substitutes the text into the code, and then applies operator precedence, so it goes:
ADD_ONE(9) * 2 = 9 + 1 * 2 = 11
The correct macro is:
#define ADD_ONE(X) (X + 1)
This is a trivial example, but illustrates the potential confusion of C macros.
How does Rust do better?
Rust has declarative macros which are syntax-aware, not just text substitutions.
In 2018 I wrote “Rust macros have to be self-contained expressions”, but I’m not sure that’s quite true. I suspect they’re more nuanced than that, but what follows is still correct.
If you try to write the C footgun macro in Rust, the compiler won’t let you. For example, the following code fails to compile with the error “no rules expected this token in macro call” on the x
:
macro_rules! plus_one {
($x:expr) => $x + 1;
}
It won’t be happy until you add the missing parentheses:
macro_rules! plus_one {
($x:expr) => ($x + 1);
}
Again, this is a trivial example, but I’m sure this mechanism protects you in more complex cases.
(I originally wrote about this on Twitter, and copied it to this site in October 2025.)