Macros

Macro Expansion

Macro expansion is the early compilation phase in which rustc iteratively replaces every macro invocation (macro!, #[derive], #[attr]) with its generated AST nodes until no macro calls remain, before running type checking, borrow checking, or LLVM code generation.

Where Macro Expansion Fits in the rustc Pipeline

Source Text (.rs)
  │
  ▼  1. Lexing & Parsing (produces initial AST + unexpanded macro nodes)
Initial AST
  │
  ▼  2. Macro Expansion & Name Resolution (fixed-point loop until all macros expand)
Fully Expanded AST  <── Every macro fragment must be a valid AST node!
  │
  ▼  3. HIR Lowering & Type Inference / Trait Resolution
  ▼  4. MIR Borrow Checking & Optimization
  ▼  5. LLVM IR & Machine Code Generation

Because macro expansion happens before type checking:

  • A macro cannot query the inferred type of an arbitrary expression variable x ($x:expr only knows the syntax tokens of x, not its resolved type).
  • Type errors inside expanded macro code are reported after expansion, pointing back to both the macro definition and the call site via span hygiene.

Recursive Macro Expansion (TT Munchers and Recursion Limits)

A macro expansion can contain another macro call—including a recursive call to the same macro. The compiler expands macros iteratively up to a configurable recursion depth (defaulting to 128, adjustable via #![recursion_limit = "256"]):

macro_rules! count_tts {
    // Base case: 0 tokens -> 0
    () => { 0usize };
    // Recursive step: peel 1 token tree `$head:tt` and recurse on `$($tail:tt)*`
    ($head:tt $($tail:tt)*) => {
        1usize + count_tts!($($tail)*)
    };
}

fn main() {
    const N: usize = count_tts!(alpha beta (1 + 2) gamma);
    println!("Token tree count: {N}");
}
Token tree count: 4

(Notice that (1 + 2) counts as a single token tree (tt) because parenthesized groups are single balanced token trees.)

AST Node Completeness Rule

Every macro expansion must produce a complete, self-contained AST construct (a complete expression, statement, pattern, type, or item). A macro cannot expand to an unclosed brace { or half of an expression like 1 +:

// ❌ does not compile
macro_rules! incomplete_expr {
    () => { 10 + };
}

fn main() {
    let x = incomplete_expr!() 20;
}
error: unexpected end of macro invocation
 --> src/main.rs:3:16
  |
3 |     () => { 10 + };
  |                ^ missing tokens in macro arguments