A macro error can point into the macro definition even when a particular call triggers it. Start with the diagnostic, then reduce the call until you can explain which rule matches and what that rule produces.
Read the message and its source locations
Check the highlighted code, the invocation location, and any note naming the originating macro. The location alone does not identify the failure stage.
- A malformed matcher can fail when the macro is defined. For example, an
exprfragment cannot be followed by every possible token. - A call can fail to match, encounter a parsing error, or be locally ambiguous. Check separators, optional trailing commas and fragment kinds.
- A matched rule can fail during expansion, name resolution or type checking. Repetition-depth errors may appear only when a rule is invoked, even if the highlighted code is in its definition.
Read the first relevant diagnostic before chasing later errors. Notes such as “this error originates in the macro” can help trace nested calls, but not every diagnostic has that exact wording.
Inspect captured tokens with stringify!
stringify! produces a string literal from tokens without evaluating them.
This example prints the captured expression and evaluates it once:
macro_rules! trace_it {
($e:expr) => {{
eprintln!("captured: {}", stringify!($e));
$e
}};
}
fn main() {
let mut calls = 0;
let answer = trace_it!({ calls += 1; 40 + 2 });
assert_eq!((answer, calls), (42, 1));
}
The string is formed at compile time, but eprintln! runs only when execution
reaches it. If the expanded program does not compile, this trace cannot run.
Temporary tracing can also affect timing, so remove it after investigating.
stringify! can accept balanced token sequences that are not valid expressions,
but $e:expr must still match an expression before this example reaches it.
Use a tt matcher when you need to inspect a more general token sequence.
It does not recursively expand macro calls contained in its input.
Do not depend on exact whitespace in its output. For example, Rust 1.98.1
prints stringify!(1+2) and stringify!(1 + 2) differently. The
stringify! documentation
warns that the resulting representation may change in future versions.
Use type errors to inspect types
A deliberate mismatch can reveal the type of an expansion. This complete example is intentionally rejected with E0308:
macro_rules! number {
() => { 40 + 2 };
}
fn main() {
let _: () = number!();
}
The diagnostic reports an integer where () was expected and points back
into the macro. It does not print a complete expansion. If the macro
already produces (), this particular probe does not create a mismatch.
Remove the deliberate error once it has answered your question.
Check metavariables and unused rules
Enable meta_variable_misuse while developing a macro to catch some problems
before the affected rule is invoked. It is allowed by default. This standalone
example compiles with a warning because $missing was never captured:
#![warn(meta_variable_misuse)]
#![allow(unused_macros)]
macro_rules! broken {
($value:expr) => { $missing };
}
fn main() {}
The lint also checks repetition misuse, but it is not a proof that every expansion is valid. It does not generally warn about a captured metavariable simply because the rule never uses it. Nested macro definitions can make its analysis imperfect; review the warning in context. See the lint documentation.
Two other lints answer different questions:
| Lint | Default | What a warning tells you |
|---|---|---|
unused_macros |
warn | A macro definition was not used in the analyzed code. |
unused_macro_rules |
allow | A rule was not used by the analyzed invocations. |
Enable the second with #![warn(unused_macro_rules)]. An unused rule may be
shadowed by an earlier rule, or its intended input may simply be absent from
your tests. Exported macros and downstream callers also limit what local
unused-code analysis can establish. Add representative calls before deciding
to remove an arm. Warnings enabled in an exercise can become grading failures
under clippy-driver -D warnings; fix the issue or remove temporary probes.
Preserve rule order while tracing a match
Rules are tried in source order. The first successful match is expanded; a later error in that expansion does not make Rust retry another arm.
In a small copy of the macro, replace each rule's output with a different marker while preserving the matchers and their order:
macro_rules! selected {
($name:ident) => { "identifier arm" };
($e:expr) => { "expression arm" };
}
fn main() {
assert_eq!(selected!(item), "identifier arm");
assert_eq!(selected!(1 + 2), "expression arm");
}
Here an identifier can match both arms, but the first wins. Deleting the first
arm would make the second match item; that would not tell you which arm the
original macro selected. Marker strings work in expression position. For
other contexts, a temporary compile_error! message in the selected rule can
identify it without running the program. Recursive macros may need markers
at only one expansion level so the probe preserves the behavior in question.
Reduce the failing call, then choose more tooling if needed
Remove one part of the input at a time, keeping changes only when the same failure remains. Preserve required delimiters and separators. Smaller calls make repetition structure easier to inspect, but a bug may require multiple elements or a particular nesting depth. Halving the input is not guaranteed to retain it.
The site's Rust runner compiles an assembled source file directly with
rustc; it does not provide a Cargo expansion-viewer command. In a local Cargo
project, cargo expand can display
expanded source. It needs a nightly compiler available for expansion, even
when your project normally uses stable Rust. Its text output is lossy and
does not preserve all hygiene information, so treat it as an inspection aid.
On a nightly toolchain, -Z macro-backtrace provides more expansion context;
trace_macros! and log_syntax! are also unstable tools. They are optional
local debugging aids, not requirements for the stable examples above.
A useful sequence is: read the diagnostic, identify the matching rule, inspect its captures, and shrink the failing invocation. Use a type probe for a type question and an expansion viewer when you need to inspect generated code.