Macros
Debugging Macro Expansion
Because macros expand into AST nodes before type checking, debugging a complex macro_rules! or procedural macro involves inspecting the exact Rust code emitted by the macro and emitting clear compile-time diagnostics when inputs are invalid.
Tools and Techniques for Inspecting Expanded Macros
| Tool / Technique | Command or Syntax | Best Used When... |
|---|---|---|
cargo expand |
cargo expand or cargo expand path::to::module |
Viewing formatted, syntax-highlighted macro expansions (#[derive], macro_rules!, #[attr]) |
rust-analyzer: Expand macro recursively |
Editor command on cursor over macro call | Inspecting a single macro invocation interactively inside VS Code / Zed / Neovim |
stringify!($tokens) |
println!("{}", stringify!(my_macro!(...))) |
Printing raw unexpanded tokens in stable code without external tools |
compile_error!("msg") |
compile_error!("invalid syntax in macro") |
Emitting custom compile-time error messages from invalid macro arms |
trace_macros!(true) (Nightly) |
#![feature(trace_macros)] trace_macros!(true); |
Tracing step-by-step recursive macro_rules! arm matching |
Inspecting Expansion Steps and Emitting Custom Errors with compile_error!
The standard library's compile_error!("message") macro unconditionally fails compilation with the given message whenever it is expanded. Placing compile_error! inside a fallback macro_rules! arm lets you replace cryptic parser errors with actionable domain-specific error messages:
macro_rules! configure_port {
(http) => {
80_u16
};
(https) => {
443_u16
};
// Custom diagnostic arm for unsupported protocol names:
($other:ident) => {
compile_error!(concat!(
"unsupported protocol `",
stringify!($other),
"`; expected `http` or `https`"
))
};
}
fn main() {
let p1 = configure_port!(http);
let p2 = configure_port!(https);
println!("http={p1}, https={p2}");
}
http=80, https=443
If a caller invokes configure_port!(ftp), compile_error! halts compilation immediately with your exact message:
// ❌ does not compile
macro_rules! configure_port {
(http) => { 80_u16 };
(https) => { 443_u16 };
($other:ident) => {
compile_error!(concat!(
"unsupported protocol `",
stringify!($other),
"`; expected `http` or `https`"
))
};
}
fn main() {
let _p = configure_port!(ftp);
}
error: unsupported protocol `ftp`; expected `http` or `https`
--> src/main.rs:6:9
|
6 | / compile_error!(concat!(
7 | | "unsupported protocol `",
8 | | stringify!($other),
9 | | "`; expected `http` or `https`"
10 | | ))
| |__________^
...
15 | let _p = configure_port!(ftp);
| -------------------- in this macro invocation