← thecodex.expert · The Codex Family of Knowledge
Rust

Macros

vec! and println! aren't functions at all — the trailing ! means they're macros, expanded into real code before the compiler even checks types.

Rust 1.95+ macro_rules! Last verified:
Where this bites you

A macro argument isn't evaluated once and handed over like a function argument — the expression itself is pasted into the template, everywhere the parameter appears. A macro that uses $x twice evaluates the argument twice, so an argument with side effects (a counter, a read, an allocation) runs twice and the result is wrong with no error anywhere.

Rustdouble_eval_bug.rs
macro_rules! square {
    ($x:expr) => { $x * $x };
}

let mut calls = 0;
let mut next_id = || { calls += 1; calls };

let result = square!(next_id());   // expands to next_id() * next_id()
println!("result = {}", result);   // result = 2   (1 * 2, not 1 * 1)
println!("calls  = {}", calls);    // calls  = 2   (the argument ran twice)
Canonical Definition

A macro generates code at compile time, before normal type checking happens — which is why vec![1, 2, 3] can accept any number of arguments of any single type, something an ordinary function's fixed signature could never do. The trailing ! is what marks a macro call and distinguishes it visually from a function call. macro_rules! defines a declarative macro by matching the call's syntax against a pattern and expanding to a code template.

Macros you already use every day

vec!, println!, format!, and assert_eq! are all macros, not functions — that's why vec! can take a variable number of comma-separated values and println! can accept a format string plus any number of arguments, neither of which an ordinary Rust function signature supports.

Rusteveryday_macros.rs
let v = vec![1, 2, 3, 4, 5];              // vec! — variable argument count
println!("{} and {}", "hello", 42);        // println! — format string + args
let s = format!("{}-{}", "a", "b");        // format! — like println! but returns a String

Writing a declarative macro with macro_rules!

A macro_rules! definition matches against one or more patterns, each producing a code template. This example creates a HashMap literal, something Rust's standard library has no built-in syntax for.

Rustmacro_rules.rs
macro_rules! hashmap {
    ($($key:expr => $val:expr),* $(,)?) => {{
        let mut map = std::collections::HashMap::new();
        $(map.insert($key, $val);)*
        map
    }};
}

let scores = hashmap!{
    "Alice" => 90,
    "Bob" => 85,
};

That's the tradeoff behind the page's note that macros are harder to reason about than functions: a function parameter is evaluated exactly once, a macro parameter is evaluated once per mention. The standard defence is to bind the argument to a local first, so the expression runs once and the template reuses the value (the extra braces make the expansion a single block expression):

Rustdouble_eval_fix.rs
macro_rules! square {
    ($x:expr) => {{
        let v = $x;     // evaluated exactly once
        v * v
    }};
}

let result = square!(next_id());
println!("result = {}", result);   // result = 1
println!("calls  = {}", calls);    // calls  = 1

Why a macro instead of a function

Macros solve problems ordinary functions structurally cannot: taking a variable number of arguments of different types, generating repetitive boilerplate code, or running entirely before type checking (which is how the derive macros behind #[derive(Debug)] work). The tradeoff is that macros are harder to read and debug than functions, so idiomatic Rust reaches for a function first and a macro only when a function genuinely cannot do the job.

Sources

1
The Rust Project. The Rust Programming Language, "Macros", doc.rust-lang.org/book/ch19-06.