When the macro definition breaks the parser
You're writing a macro to eliminate repetitive boilerplate. You define the rules. You invoke the macro. The compiler halts with "macro expansion ignores token". The error points to a line inside your macro_rules! block. You check the invocation. It matches the pattern perfectly. The issue isn't the call. The macro definition contains a syntax error that breaks the parser.
This error rarely comes from how you call a macro. It comes from how you wrote the macro. The macro engine encountered a token in the definition that it couldn't fit into the macro syntax structure. The parser entered error recovery mode and skipped the token to keep going. The message tells you the parser dropped something because the definition is malformed.
How macro parsing works
Rust macros run before the type checker. The macro engine reads your macro_rules! definition and builds a set of patterns. Each pattern has a matcher and a body. The matcher describes what input tokens the macro accepts. The body describes what code to generate.
When you invoke the macro, the engine matches the input against the patterns. If a match succeeds, the engine replaces the macro call with the body, substituting metavariables with the matched tokens. If no match succeeds, the compiler reports "no rules expected the token". That's a call-site error.
"Macro expansion ignores token" is different. It happens during the parsing of the definition itself. The macro parser is a recursive descent parser for macro syntax. It expects specific tokens in specific positions. If it sees a token that doesn't belong, it can't build the rule tree. The parser skips the token to recover and continues parsing. The error message flags the skipped token.
Common triggers include typos in fragment specifiers, mismatched delimiters, missing semicolons between rules, or stray tokens in the body. The error points to the definition, not the invocation. Check the macro_rules! block first.
Minimal example
This macro definition compiles and works correctly. The matcher uses valid fragment specifiers and delimiters. The body produces valid Rust code.
/// A macro that creates a simple struct with one field.
/// The matcher captures the struct name, field name, and field type.
macro_rules! create_struct {
($name:ident, $field:ident : $type:ty) => {
struct $name {
$field: $type,
}
}
}
fn main() {
// The macro expands to a struct definition.
// The invocation matches the pattern exactly.
create_struct!(Point, x: i32);
let p = Point { x: 10 };
println!("x = {}", p.x);
}
The definition has a single rule. The matcher ($name:ident, $field:ident : $type:ty) captures three metavariables. The body generates a struct. The compiler parses this definition without issues.
If you introduce a typo in the definition, the parser fails. For example, writing $type:typ instead of $type:ty causes the parser to ignore the token. The error points to the typo. The fix is to correct the specifier.
Macro syntax rules
Macro definitions follow strict syntax rules. Violating these rules triggers parser errors. Understanding the syntax helps you spot mistakes quickly.
Matchers use fragment specifiers to capture tokens. Valid specifiers include expr, ident, ty, pat, block, stmt, item, meta, path, lifetime, and tt. Each specifier matches a specific kind of Rust syntax. expr matches expressions. ident matches identifiers. ty matches types. pat matches patterns. block matches blocks like { ... }. stmt matches statements. item matches items like functions or structs. meta matches attribute metadata. path matches paths. lifetime matches lifetimes. tt matches a single token tree.
Repetition syntax allows matching multiple tokens. Use $(...)* for zero or more repetitions. Use $(...)+ for one or more. Use $(...)? for zero or one. The repetition must have a delimiter and a separator. For example, $( $x:expr ),* matches a comma-separated list of expressions. The body must use the same repetition syntax to generate code.
Delimiters must match. Matchers use (), {}, or []. The body must use the same delimiters as the matcher. Mismatched delimiters cause parser errors.
Semicolons separate multiple rules. If a macro has more than one rule, each rule must end with a semicolon. Missing semicolons cause the parser to ignore tokens.
Convention aside: The community convention is to avoid tt unless necessary. tt matches any token tree, which can lead to ambiguous rules and hard-to-debug macros. Prefer specific specifiers like expr or ident to catch errors early and make the macro's intent clear.
Walkthrough of the error
When the macro parser encounters an error, it doesn't stop immediately. It tries to recover. Recovery involves skipping tokens until the parser finds a valid continuation. This allows the compiler to report multiple errors in a single pass.
The "macro expansion ignores token" message indicates the parser skipped a token during recovery. The token might look correct in isolation. The problem is the surrounding structure. For example, a stray brace in the matcher confuses the parser. The parser skips the brace and continues. The error points to the brace.
The error message includes the location of the ignored token. Check that location in the macro definition. Look for typos, missing delimiters, or structural mistakes. The invocation is usually innocent.
Realistic example
This macro generates a struct with multiple fields. It uses repetition syntax to handle an arbitrary number of fields. The definition is correct and compiles.
/// A macro that creates a struct with multiple fields.
/// The matcher captures the struct name and a list of fields.
/// Each field has a name and a type.
macro_rules! create_struct_multi {
($name:ident, $($field:ident : $type:ty),*) => {
struct $name {
$($field: $type,)*
}
}
}
fn main() {
// The macro expands to a struct with two fields.
// The invocation matches the repetition pattern.
create_struct_multi!(Point, x: i32, y: i32);
let p = Point { x: 1, y: 2 };
println!("x = {}, y = {}", p.x, p.y);
}
The matcher uses $( $field:ident : $type:ty ),* to capture a comma-separated list of fields. The body uses $($field: $type,)* to repeat the fields in the struct definition. The trailing comma in the body is safe because Rust allows trailing commas in struct definitions.
If you forget the comma separator in the matcher, the parser fails. For example, writing $( $field:ident : $type:ty )* without the comma causes the parser to ignore tokens. The error points to the repetition syntax. The fix is to add the separator.
Pitfalls and debugging
Macro errors can be cryptic. The error message points to a token, but the root cause might be elsewhere. Here are common pitfalls.
Missing semicolons between rules cause the parser to ignore tokens. If a macro has multiple rules, ensure each rule ends with a semicolon. The error points to the start of the next rule.
Mismatched delimiters cause parser errors. If the matcher uses (), the body must use (). If the matcher uses {}, the body must use {}. The error points to the mismatched delimiter.
Typos in fragment specifiers cause the parser to ignore tokens. Writing $x:exp instead of $x:expr triggers the error. The error points to the typo. Check the specifier spelling.
Stray tokens in the body cause parser errors. If the body contains a token that isn't valid Rust syntax, the parser ignores it. The error points to the stray token. Check the body for syntax errors.
Convention aside: Use cargo expand to debug macros. Install the cargo-expand crate as a dev-dependency. Run cargo expand to see the source code after macro expansion. If the macro definition is broken, cargo expand might fail or show partial output. If the macro parses but produces bad code, cargo expand shows the raw output. Inspect the output to find the issue. cargo expand is the standard tool for macro debugging in the Rust community.
The compiler error "macro expansion ignores token" has no famous E-code. It's a parser diagnostic. Inline the error in your mental model: the parser skipped a token because the definition is malformed. Check the definition. Fix the syntax.
Decision: when to use macros
Macros are powerful, but they add complexity. Use them only when necessary.
Use macro_rules! when you need compile-time code generation based on syntax patterns. Use it for repetitive boilerplate, DSLs, or conditional compilation based on syntax structure. Keep the macro simple. Avoid complex logic.
Use functions when the logic is fixed and doesn't depend on syntax structure. Functions are easier to read, debug, and test. Prefer functions over macros when possible.
Use proc_macro when you need to parse arbitrary syntax or generate code based on complex analysis. Procedural macros run as separate crates and have full access to the token stream. Use them for derive macros, attribute macros, or function-like macros that require advanced parsing.
Use const blocks when you need compile-time evaluation of expressions without generating new syntax. const blocks allow compile-time computation without macro complexity. Prefer const blocks over macros for simple compile-time values.
Check the definition. The call site is innocent.
Run cargo expand. If the output is garbage, the macro definition is broken.
Macros are code generators. A broken generator produces broken code.