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.
More than one rule
Section titled “More than one rule”A macro can have several rules, separated by semicolons. Rust tries them from top to bottom and uses the first one that matches. A rule can even call the macro again. Here’s a max! that works with any number of arguments:
macro_rules! max { // Rule 1: just one value. The biggest is the value itself. ($x:expr) => { $x }; // Rule 2: a value, then one or more others. ($x:expr, $($rest:expr),+) => { { let first = $x; let biggest_of_rest = max!($($rest),+); if first > biggest_of_rest { first } else { biggest_of_rest } } };}
fn main() { println!("{}", max!(7)); println!("{}", max!(3, 9, 4)); println!("{}", max!(2.5, 1.0));}Output:
792.5For max!(3, 9, 4), rule 1 doesn’t match (there’s more than one value), so rule 2 does. It keeps 3 aside and expands max!(9, 4), which in turn expands max!(4). That one matches rule 1 and stops. The order matters: Rust always tries rule 1 first.
Two small new pieces of syntax here:
+means one or more, while*(fromsum!) means zero or more. Rule 2 needs at least one value after the comma, so the innermax!(...)call always has something to work with.max!works for integers and for floats, because the macro just pastes in the codefirst > biggest_of_rest. A function would need generics to do that.
expr isn’t the only kind of thing a macro can accept. A few others you’ll see in real macros:
| Fragment | Matches | Example |
|---|---|---|
expr |
any expression | 2 + 3, name.len() |
ident |
a name | count, Point |
ty |
a type | i32, Vec<String> |
literal |
a literal value | 42, "hi" |
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.
Practice
Section titled “Practice”1. Print everything
Section titled “1. Print everything”Write a macro print_all! that takes any number of values, separated by commas, and prints each one on its own line. print_all!("apple", 42, 3.5) should print three lines. Hint: match $($x:expr),* like sum! does, and repeat a println! for each one with $( ... )*.
Show solution
macro_rules! print_all { ($($x:expr),*) => { $(println!("{}", $x);)* };}
fn main() { print_all!("apple", 42, 3.5);}Output:
apple423.5The macro expands into three separate println! calls, one per argument. Each value can have a different type, which a normal function couldn’t handle.
2. Your own vec!
Section titled “2. Your own vec!”Write my_vec! that works like vec!. my_vec![] gives an empty Vec, and my_vec![1, 2, 3] gives a Vec with those items. Hint: use two rules. The first matches nothing at all, (). The second matches $($x:expr),+, makes a Vec::new(), and pushes each $x into it.
Show solution
macro_rules! my_vec { () => { Vec::new() }; ($($x:expr),+) => { { let mut v = Vec::new(); $(v.push($x);)+ v } };}
fn main() { let empty: Vec<i32> = my_vec![]; let numbers = my_vec![1, 2, 3]; println!("{:?} {:?}", empty, numbers);}Output:
[] [1, 2, 3]You can call any macro with (), [] or {}. People use square brackets for list-like macros such as vec!, but my_vec!(1, 2, 3) works too. The empty Vec needs the : Vec<i32> type, because with no items Rust can’t tell what it will hold.
- 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),*).- A macro can have several rules. Rust tries them top to bottom, and a rule can call the macro again.
- 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
Read more
Section titled “Read more”- Macros — The Rust Book: how
vec!is defined, plus an introduction to procedural macros like#[derive]. macro_rules!— Rust by Example: short, runnable examples of designators, overloading and repetition.- Macros By Example — The Rust Reference: the full list of fragment types like
expr,identandtt, and exactly how matching works. - The Little Book of Rust Macros: a free, friendly deep dive for when you want to write bigger macros.