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.
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)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.
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 StringWriting 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.
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):
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 = 1Why 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.