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.96 macro_rules! Last verified:
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,
};

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.