411: macro_rules! Declarative Macros
Difficulty: 3 Level: Advanced Write code that writes code โ match on syntax patterns and expand them at compile time, with zero runtime cost.The Problem This Solves
Some code patterns can't be abstracted with functions. `assert_eq!(a, b)` prints the source text of both expressions on failure โ that's not possible with a function, because a function only sees values, not the code that produced them. `println!("{}", x)` accepts variable argument counts โ that's not possible with Rust's type system without variadics (which Rust doesn't have). `vec![1, 2, 3]` constructs a `Vec` with literal syntax โ a function call can't achieve this ergonomics. Macros operate on the syntax tree before the compiler processes types and values. They transform one piece of syntax into another. The input is tokens; the output is tokens; the result is compiled normally. This is why `assert_eq!` can display source text, why `println!` can take any number of arguments, and why `vec![]` feels like a language feature. `macro_rules!` is Rust's declarative macro system. You write pattern-matching rules: if the invocation matches this pattern, expand to that code. It's hygienic (identifiers in the macro don't leak into caller scope), it's zero-cost (all work happens at compile time), and it composes with the rest of the type system.The Intuition
A `macro_rules!` macro is a set of match arms, like a `match` expression but for syntax rather than values. Each arm has a pattern (what to match in the invocation) and a template (what to expand to). The compiler tries each arm in order and expands the first match. Fragment specifiers (`$name:expr`, `$name:ident`, `$name:ty`) capture different kinds of syntax. The captured fragments are substituted into the template. Multiple arms let one macro name handle different call signatures โ like overloaded functions, but for syntax.How It Works in Rust
// Multiple arms: handles with or without format args
macro_rules! log_info {
($msg:expr) => {
println!("[INFO] {}", $msg)
};
($fmt:expr, $($arg:expr),*) => {
println!(concat!("[INFO] ", $fmt), $($arg),*)
};
}
// Creates a HashMap with literal syntax
macro_rules! map {
($($k:expr => $v:expr),* $(,)?) => {
{
let mut m = std::collections::HashMap::new();
$(m.insert($k, $v);)* // repeat for each k => v pair
m
}
};
}
// min of any number of values
macro_rules! min_of {
($a:expr) => { $a }; // base case
($a:expr, $($rest:expr),+) => { // recursive case
{
let rest_min = min_of!($($rest),+);
if $a < rest_min { $a } else { rest_min }
}
};
}
fn main() {
log_info!("Application started");
log_info!("User {} logged in at port {}", "Alice", 8080);
let m = map! {
"one" => 1,
"two" => 2,
"three" => 3, // trailing comma: $(,)? handles it
};
println!("{:?}", m);
println!("min(3,7,1,9) = {}", min_of!(3, 7, 1, 9));
}
Fragment specifiers quick reference:
| Specifier | Matches |
|---|---|
| `expr` | Any expression: `1 + 2`, `"hello"`, `foo()` |
| `ident` | An identifier: `my_var`, `println` |
| `ty` | A type: `i32`, `Vec<String>`, `Option<T>` |
| `pat` | A pattern: `Some(x)`, `Ok(v)`, `1..=5` |
| `literal` | A literal: `42`, `"hello"`, `3.14` |
| `block` | A block: `{ stmt; expr }` |
| `stmt` | A statement |
| `tt` | A single token tree (any single token or `()[]{}` group) |
What This Unlocks
- Variadic APIs โ `vec![]`, `println!`, `assert_eq!`, `format!` โ all use macros to achieve variable argument count, impossible with regular functions.
- Compile-time code generation โ `map!{}`, `impl_from_int!{}` โ generate repetitive boilerplate at compile time without runtime cost.
- Domain-specific assertion utilities โ `check_eq!`, custom `debug!` macros with context โ richer diagnostics than a function could provide.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Metaprogramming | PPX (preprocessing extensions) โ complex, separate tool | `macro_rules!` โ built-in, hygienic, integrated with compiler |
| Variadic | Lists: `log "INFO" "msg"` โ no variadic syntax | Macros: `log_info!("msg", arg1, arg2)` โ variadic via repetition patterns |
| Hygiene | PPX is not hygienic by default | `macro_rules!` is hygienic โ introduced identifiers don't leak |
| Compile-time | PPX runs before compilation | `macro_rules!` expands during compilation, same pipeline |
// macro_rules! declarative macros in Rust
// Simple logging macro
macro_rules! log_info {
($msg:expr) => {
println!("[INFO] {}", $msg)
};
($fmt:expr, $($arg:expr),*) => {
println!(concat!("[INFO] ", $fmt), $($arg),*)
};
}
// assert with custom message (like assert_eq! but custom)
macro_rules! check_eq {
($left:expr, $right:expr) => {
if $left != $right {
panic!("check_eq failed: {} != {}", $left, $right);
}
};
($left:expr, $right:expr, $msg:expr) => {
if $left != $right {
panic!("check_eq failed ({}): {} != {}", $msg, $left, $right);
}
};
}
// repeat macro
macro_rules! repeat {
($n:expr, $body:block) => {
for _ in 0..$n $body
};
}
// min/max for any number of args
macro_rules! min_of {
($a:expr) => { $a };
($a:expr, $($rest:expr),+) => {
{
let rest_min = min_of!($($rest),+);
if $a < rest_min { $a } else { rest_min }
}
};
}
macro_rules! max_of {
($a:expr) => { $a };
($a:expr, $($rest:expr),+) => {
{
let rest_max = max_of!($($rest),+);
if $a > rest_max { $a } else { rest_max }
}
};
}
// Map literal macro
macro_rules! map {
($($k:expr => $v:expr),* $(,)?) => {
{
let mut m = std::collections::HashMap::new();
$(m.insert($k, $v);)*
m
}
};
}
fn main() {
log_info!("Application started");
log_info!("User {} logged in at port {}", "Alice", 8080);
check_eq!(2 + 2, 4, "basic arithmetic");
check_eq!("hello".len(), 5);
print!("Repeating: ");
repeat!(3, { print!("Ho "); });
println!();
println!("min(3,7,1,9) = {}", min_of!(3, 7, 1, 9));
println!("max(3,7,1,9) = {}", max_of!(3, 7, 1, 9));
let m = map! {
"one" => 1,
"two" => 2,
"three" => 3,
};
println!("Map: {:?}", m);
}
#[cfg(test)]
mod tests {
#[test]
fn test_min_max() {
assert_eq!(min_of!(5, 3, 7, 1, 9), 1);
assert_eq!(max_of!(5, 3, 7, 1, 9), 9);
}
#[test]
fn test_map_macro() {
let m = map! { "a" => 1, "b" => 2 };
assert_eq!(m["a"], 1);
assert_eq!(m["b"], 2);
}
#[test]
#[should_panic]
fn test_check_eq_fails() {
check_eq!(1, 2);
}
}
(* Declarative macros in OCaml via ppx and module-level patterns *)
(* No direct macro_rules! equivalent; we show patterns with functions *)
(* Pattern: log with file/line info *)
let log level msg =
Printf.printf "[%s] %s\n" level msg
(* Pattern: assert with message *)
let assert_eq_msg a b msg =
if a <> b then
failwith (Printf.sprintf "Assertion failed: %s (got %d, expected %d)" msg a b)
(* Pattern: repeat *)
let repeat n f =
for _ = 1 to n do f () done
(* Pattern: min/max macros *)
let min_of a b = if a < b then a else b
let max_of a b = if a > b then a else b
let () =
log "INFO" "Starting application";
assert_eq_msg (2 + 2) 4 "arithmetic";
repeat 3 (fun () -> Printf.printf "Hello!\n");
Printf.printf "min(3,7)=%d max(3,7)=%d\n" (min_of 3 7) (max_of 3 7)