title: Macros
Macros are code that writes code. They are invoked with a ! suffix (println!, vec!) and run at compile time, generating Rust code before the compiler sees it.
There are two main kinds:
- Declarative macros (
macro_rules!) — pattern-match on syntax and expand to code - Procedural macros — functions that receive a token stream and return a token stream (used for
#[derive(...)]and similar attributes)
This page focuses on declarative macros, which are the most common.
Why macros exist
Some things can’t be done with functions:
- Functions have a fixed number of parameters —
println!accepts any number - Functions can’t accept mixed types in a single variadic argument list
- Functions can’t generate
implblocks or struct fields
Macros solve these by operating on the syntax itself, before types are resolved.
println!
println! prints a line to stdout. It takes a format string and any number of arguments.
println!("hello");
println!("x = {}", x);
println!("x = {x}"); // captured variable syntax (Rust 1.58+)
println!("{:?}", some_val); // debug formatThe format string is checked at compile time — a mismatch between placeholders and arguments is a compile error.
What it expands to
println!("x = {}", x);expands roughly to:
{
use std::io::Write;
let stdout = std::io::stdout();
let mut handle = stdout.lock();
handle.write_fmt(format_args!("x = {}\n", x)).unwrap();
}format_args! is a compiler built-in that constructs an Arguments struct from the format string and values — no heap allocation. println! locks stdout, writes the formatted output, and appends a newline.
The {} placeholders call Display::fmt. Use {:?} for Debug::fmt and {:#?} for pretty-printed debug output.
Related macros
| Macro | Behaviour |
|---|---|
print! | same as println! but no trailing newline |
eprintln! | prints to stderr |
format! | same formatting, returns a String |
write! / writeln! | writes to any Write implementor |
vec!
vec! creates a Vec<T> from a list of values.
let v = vec![1, 2, 3];
let v = vec!["a", "b", "c"];
let v = vec![0; 5]; // five zeros: [0, 0, 0, 0, 0]What it expands to
let v = vec![1, 2, 3];expands to:
let v = {
let mut temp = Vec::new();
temp.push(1);
temp.push(2);
temp.push(3);
temp
};In practice the standard library uses a slightly more optimised form with with_capacity to avoid reallocations:
let v = {
let mut temp = Vec::with_capacity(3);
temp.push(1);
temp.push(2);
temp.push(3);
temp
};The repeat form vec![value; n] expands to vec::from_elem(value, n), which calls Clone on value n times.
You can inspect what any macro expands to with cargo expand (from the cargo-expand tool):
cargo expand
Writing a simple macro
Declarative macros use macro_rules! and match on syntax patterns.
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
say_hello!(); // prints: Hello!Patterns can capture arguments:
macro_rules! double {
($x:expr) => {
$x * 2
};
}
let n = double!(5); // expands to: 5 * 2:expr is a fragment specifier — it tells the macro what kind of syntax to match. Common ones:
| Specifier | Matches |
|---|---|
expr | any expression |
ident | an identifier (variable/function name) |
ty | a type |
stmt | a statement |
block | a { ... } block |
tt | a single token tree (catch-all) |
Variadic patterns
Use $(...)* to match zero or more repetitions, or $(...)+ for one or more.
macro_rules! my_vec {
( $( $x:expr ),* ) => {
{
let mut temp = Vec::new();
$(
temp.push($x);
)*
temp
}
};
}
let v = my_vec![1, 2, 3];The $( $x:expr ),* pattern matches a comma-separated list of expressions. Inside the expansion, $( temp.push($x); )* repeats the push for each captured $x.
Hygiene
Macros in Rust are hygienic — identifiers introduced inside a macro don’t accidentally collide with identifiers at the call site.
macro_rules! make_x {
() => {
let x = 42;
};
}
let x = 1;
make_x!();
println!("{x}"); // prints 1, not 42 — the macro's x is separateThis is different from C preprocessor macros, which do simple text substitution and have no hygiene.
Procedural macros
Procedural macros are more powerful and are used for things like #[derive(Debug)], #[derive(Serialize)], or custom attribute macros. They are defined in a separate crate and operate on the compiler’s token stream directly. That’s a larger topic worth its own page.