Macro Matching
Macro matching is the top-down pattern-matching algorithm used by macro_rules! to test an invocation's input token stream against each rule's matcher pattern (($pattern) => { $expansion };) and bind matched grammar fragments to metavariables ($name:designator).
Top-Down Rule Evaluation and No-Backtracking Within Fragment Specifiers
When a macro_rules! macro is invoked:
- The expander tests each rule in top-to-bottom declaration order and selects the first rule whose matcher completely consumes the input token stream.
- When matching a typed fragment specifier (
$e:expr,$t:ty,$p:pat,$s:stmt,$i:item), the parser parses the fragment greedily without backtracking.
Because typed fragment parsers do not backtrack, Rust enforces follow-set restrictions: the token immediately following a fragment specifier must belong to that specifier's allowed follow set, or the macro definition itself is rejected at compile time!
Complete Reference of Fragment Specifiers and Their Follow Sets (Rust 2024 Edition)
| Specifier | Matches | Allowed Following Tokens (Follow Set) |
|---|---|---|
expr |
Any valid Rust expression (including _ and const {} in 2024) |
=>, ,, ; (plus closing ), ], }) |
expr_2021 |
Pre-2024 expression (excludes _ and const {} expressions) |
=>, ,, ; |
stmt |
A statement without its trailing semicolon | =>, ,, ; |
ty |
Any type (u32, Vec<T>, &'a str, impl Trait) |
=>, ,, ;, =, |, :, >, >>, [, {, as, where, or a block |
pat |
Any pattern (including top-level | or-patterns: A | B) |
=>, ,, =, if, in |
pat_param |
Single pattern without top-level | |
=>, ,, =, |, if, in, : |
path |
A TypeStyle path (std::mem::replace, Vec::<u8>) |
Same as ty |
ident |
An identifier or keyword (foo, self, r#type) |
Any token |
lifetime |
A lifetime token ('a, 'static, '_) |
Any token |
literal |
A literal (42, -10, "hi", 'c', true) |
Any token |
block |
A { ... } block expression |
Any token |
item |
A module-level item (fn, struct, impl, use) |
Any token |
meta |
The contents of an attribute (derive(Debug)) |
Any token |
vis |
An optional visibility qualifier (pub, pub(crate), or empty!) |
Almost any token starting an item or ident |
tt |
A single token tree (single token or balanced ()/[]/{}) |
Any token |
Follow-Set Compile Error ($a:expr $b:expr)
Attempting to follow $a:expr with whitespace or an arbitrary operator like -> (which could be ambiguous or outside expr's follow set =>, ,, ;) fails when compiling the macro definition:
// ❌ does not compile
macro_rules! bad_arrow {
// Error: `$lhs:expr` can only be followed by `=>`, `,`, or `;` (not `->`)!
($lhs:expr -> $rhs:expr) => {
($lhs, $rhs)
};
}
fn main() {
let _ = bad_arrow!(1 -> 2);
}
error: `$lhs:expr` is followed by `->`, which is not allowed for `expr` fragments
--> src/main.rs:4:16
|
4 | ($lhs:expr -> $rhs:expr) => {
| ^^ not allowed after `expr` fragments
|
= note: allowed there are: `=>`, `,` or `;`
Using => (which is in the follow set of expr) or wrapping $lhs:expr inside delimiters ($lhs:expr) -> ($rhs:expr) resolves the ambiguity cleanly.
Ordering Rules from Most Specific to Most General
Because macro_rules! selects the first matching rule from top to bottom, place specific literal/keyword rules above general catch-all $e:expr or $($tt:tt)* rules:
macro_rules! route {
// Specific literal/keyword rules first:
(GET $path:literal => $handler:ident) => {
println!("registered GET route {} -> {}", $path, stringify!($handler))
};
(POST $path:literal => $handler:ident) => {
println!("registered POST route {} -> {}", $path, stringify!($handler))
};
// General fallback rule last:
($method:ident $path:literal => $handler:ident) => {
println!(
"registered custom {} route {} -> {}",
stringify!($method),
$path,
stringify!($handler)
)
};
}
fn main() {
route!(GET "/health" => check_health);
route!(POST "/users" => create_user);
route!(DELETE "/users/1" => delete_user);
}
registered GET route /health -> check_health
registered POST route /users -> create_user
registered custom DELETE route /users/1 -> delete_user