Macros

Why Macros Exist

A macro in Rust is a compile-time metaprogramming construct that consumes a sequence of syntax tokens and expands into valid Rust abstract syntax tree (AST) nodes before type checking begins.

What Macros Can Do That Functions Cannot

Regular Rust functions (fn) must declare a fixed number of parameters with concrete or generic types, evaluate all arguments eagerly before entering the function body, and cannot generate new items (struct, impl, fn) at compile time. Macros exist to solve problems that require operating on syntax rather than runtime values:

Capability Regular Function (fn) Macro (macro!, #[derive], #[attr])
Variadic arguments (println!("{a} {b}"), vec![1, 2, 3]) No — arity is fixed in the signature Yes — matches arbitrary repetition counts
Generating items & trait impls (#[derive(Debug)]) No — can only compute values Yes — emits struct, enum, impl, and fn blocks
Compile-time format/syntax validation (format!, sql!) No — strings are opaque at runtime Yes — parses and checks tokens at compile time
Lazy / control-flow expansion (assert!, matches!, try!) No — arguments evaluate eagerly; cannot return from caller Yes — expands directly inside the caller's scope
Inspecting caller source location (file!(), line!(), dbg!) Only via #[track_caller] at runtime Yes — expands to literal source file/line/expression text

The Two Macro Families in Rust

Rust Macros
  ├── 1. Declarative Macros (`macro_rules!`)
  │        └── Pattern-matching rewrite rules over syntax fragments (`vec!`, `println!`, `assert_eq!`)
  │
  └── 2. Procedural Macros (Compiled Rust functions: `TokenStream -> TokenStream`)
           ├── Derive macros:         `#[derive(Serialize, Clone)]`
           ├── Attribute-like macros: `#[tokio::main]`, `#[route(GET, "/")]`
           └── Function-like macros:  `sql!("SELECT id FROM users")`

Eliminating Repetitive Trait Implementations with a Macro

When the same trait implementation must be written across a dozen primitive types that do not share a common trait in std, a macro generates all implementations without duplicating code:

trait ZeroValue {
    const ZERO: Self;
}

macro_rules! impl_zero_for_primitives {
    ($($t:ty),* $(,)?) => {
        $(
            impl ZeroValue for $t {
                const ZERO: Self = 0 as $t;
            }
        )*
    };
}

impl_zero_for_primitives!(i8, i16, i32, i64, u8, u16, u32, u64, f32, f64);

fn main() {
    println!("i32::ZERO = {}, f64::ZERO = {:.1}", i32::ZERO, f64::ZERO);
}
i32::ZERO = 0, f64::ZERO = 0.0