Rust Macros
Back on the Syntax page, you learned the rule of thumb: a ! means it’s a macro, not a regular function — println!, vec!, format!. This page finally explains what that actually means.
Why println! can’t be a regular function
Section titled “Why println! can’t be a regular function”A regular function like fn add(a: i32, b: i32) -> i32 always takes a fixed number of arguments of fixed types. But you can call println! with wildly different argument counts and types:
fn main() { println!("no arguments here"); println!("{}", 42); println!("{} and {}", "two", 2);}Output:
no arguments here42two and 2No ordinary function signature could accept all of those calls. Macros work differently: they run before your code is fully compiled, generating actual Rust code based on however they were called. println!("{} and {}", "two", 2) effectively expands into real code that handles exactly those two arguments — a different expansion for every different call.
Writing a simple macro
Section titled “Writing a simple macro”You can define your own macros with macro_rules!. Here’s one that squares a number:
macro_rules! square { ($x:expr) => { $x * $x };}
fn main() { println!("{}", square!(5));}Output:
25($x:expr) says “accept one expression, and call it $x.” The => { ... } part is the code the macro expands into — everywhere you write square!(5), the compiler substitutes 5 * 5 before actually compiling anything.
A macro that accepts multiple arguments
Section titled “A macro that accepts multiple arguments”Macros can match more complex patterns, including a repeating list of arguments — this is something a regular function simply can’t do:
macro_rules! sum { ($($x:expr),*) => { { let mut total = 0; $(total += $x;)* total } };}
fn main() { println!("{}", sum!(1, 2, 3)); println!("{}", sum!(10, 20));}Output:
630$($x:expr),* means “zero or more expressions, separated by commas” — the same pattern that lets vec![1, 2, 3] and vec![1, 2, 3, 4, 5] both work with the one macro definition.
Where you’ll actually meet macros
Section titled “Where you’ll actually meet macros”You’ll use macros constantly (println!, vec!, format!, #[derive(Debug)]) without ever needing to write your own. Writing custom macros is a niche skill mostly used by library authors who need to eliminate genuinely repetitive boilerplate across a large codebase — it’s good to know they exist and roughly how they work, not something to reach for early on.
- A macro expands into real code before compilation, which is how
println!can accept a different number and type of arguments each time it’s called. !after a name is always the sign you’re calling a macro, not a function.macro_rules!lets you define your own, matching patterns like a single expression ($x:expr) or a repeated list ($($x:expr),*).- Writing macros is a niche skill — using the ones already in the standard library covers almost everything you’ll need.
Quick check
Section titled “Quick check”Quick check
1. Why can't println! be written as a regular function?
2. What does $x:expr mean inside a macro_rules! pattern?
3. When do macros expand into real code?
Score: 0 / 3