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