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