Rust Macros
A macro is code that writes code. Calls such as println! and vec! look like functions, yet the exclamation mark reveals a macro. A macro behaves like a stamp: you press it once, and it leaves a whole pattern of text behind. Rust expands macros during compilation, long before the program runs.
Why Macros Exist
Functions have fixed parameter lists. The println! macro accepts any number of values of any type, which no ordinary function can do. Macros remove repeated boilerplate and generate code that depends on the caller's input.
| Feature | Function | Macro |
|---|---|---|
| Runs at | Run time | Compile time (expands code) |
| Number of arguments | Fixed | Flexible |
| Called with | name() | name!() |
| Ease of reading | Simple | Harder |
Your First Macro
The macro_rules! keyword defines a declarative macro. The body contains patterns and the code each pattern produces.
macro_rules! say_hello {
() => {
println!("Hello from a macro!");
};
}
fn main() {
say_hello!();
}
Hello from a macro!
say_hello!() --[ compiler expands ]--> println!("Hello from a macro!");
Macros with Arguments
A pattern can capture pieces of the caller's code. Each capture has a name starting with a dollar sign and a fragment type.
macro_rules! square {
($x:expr) => {
$x * $x
};
}
fn main() {
println!("{}", square!(7));
println!("{}", square!(2 + 3));
}
49 25
The expr fragment captures a full expression, so 2 + 3 stays grouped and the second line prints 25 instead of 11.
Common Fragment Types
| Fragment | Matches |
|---|---|
| expr | An expression such as 5 + 2 |
| ident | A name such as counter |
| ty | A type such as i32 |
| tt | Any single token tree |
| literal | A literal value such as 10 or "text" |
Repetition
The syntax $( ... ),* matches a comma-separated list of any length. The matching syntax in the output repeats the code for each item.
macro_rules! my_vec {
( $( $item:expr ),* ) => {
{
let mut v = Vec::new();
$( v.push($item); )*
v
}
};
}
fn main() {
let numbers = my_vec![10, 20, 30];
println!("{:?}", numbers);
}
[10, 20, 30]
my_vec![10, 20, 30]
|
v
{ let mut v = Vec::new();
v.push(10); v.push(20); v.push(30);
v }
Multiple Patterns
A macro may offer several arms, and the compiler picks the first one that fits.
macro_rules! max_of {
($a:expr) => { $a };
($a:expr, $($rest:expr),+) => {
{
let other = max_of!($($rest),+);
if $a > other { $a } else { other }
}
};
}
fn main() {
println!("{}", max_of!(4, 19, 7));
}
19
The macro calls itself on the remaining items, a technique known as recursion.
Generating Items
Macros can write functions or structs, which removes repeated typing.
macro_rules! make_greeter {
($name:ident, $word:expr) => {
fn $name() {
println!("{}", $word);
}
};
}
make_greeter!(say_hi, "Hi");
make_greeter!(say_bye, "Goodbye");
fn main() {
say_hi();
say_bye();
}Procedural Macros
A second family, called procedural macros, runs real Rust code at compile time to transform other code. Three forms exist.
- Derive macros, such as
#[derive(Debug, Clone)], add trait implementations. - Attribute macros, such as
#[tokio::main], wrap or change an item. - Function-like macros look like
name!(...)and process raw tokens.
Procedural macros live in their own crate and need the syn and quote helper crates. Most developers use them without writing them.
Inspecting Expanded Code
The cargo expand tool, installed as an add-on, prints the code a macro produces. Seeing the expansion explains confusing compiler messages.
Writing Macros Responsibly
- Prefer a function when a function can do the job.
- Give macros clear names and document each pattern.
- Keep arms small and test the macro with several inputs.
- Wrap multi-statement bodies in an extra pair of braces to avoid name clashes.
Hygiene
Variables created inside a macro do not collide with variables of the same name in the caller's code. The compiler keeps the two scopes apart, so a temporary named v inside the macro never overwrites the caller's own v.
Key Points
- Macros generate code at compile time and end with
!when called. macro_rules!matches patterns and produces matching code.- Repetition syntax handles lists of any length.
- Derive and attribute macros come from procedural macro crates.
- Choose a function first and a macro only when needed.
