Skip to content

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 here
42
two and 2
▶ Try it Yourself

No 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.

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
▶ Try it Yourself

($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.

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:

6
30
▶ Try it Yourself

$($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.

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.