Use cargo expand to inspect the code generated by your macros before compilation, as this reveals the exact syntax the compiler sees and helps identify expansion errors. You can also use cargo expand with specific filters to isolate problematic macro invocations or combine it with cargo check to catch type errors in the expanded output.
First, install the cargo-expand binary if you haven't already:
cargo install cargo-expand
Once installed, run cargo expand in your project root to see the fully expanded source code for your entire crate:
cargo expand
If your project is large or you only need to debug a specific module, you can filter the output to a specific item or module. For example, to expand only the my_macro invocation within lib.rs:
cargo expand --lib --item my_macro
This is particularly useful when a macro generates complex generic code or recursive structures that are hard to trace mentally. If the macro fails to compile, the error message often points to the expanded code rather than the macro definition itself. By running cargo expand, you can see the generated code that caused the panic, allowing you to fix the logic inside the macro definition rather than guessing.
For more granular debugging, you can pipe the output to a file to inspect it in your editor with syntax highlighting:
cargo expand > expanded.rs
If you encounter issues where cargo expand itself fails to compile the project (which can happen with unstable features or specific build scripts), ensure you are using the latest version of the tool and that your Cargo.toml dependencies are up to date. Remember that cargo expand runs the macro expansion phase but does not run the full type checking pass in all configurations, so it is best used to verify syntax and structure before running cargo check or cargo build to catch type mismatches.
In complex scenarios involving procedural macros, cargo expand is indispensable because it bypasses the abstraction layer, showing you exactly what tokens are being emitted. This direct visibility is the most efficient way to resolve "macro expansion failed" errors without needing to manually trace through the macro's token stream manipulation logic.