macro_rules! in Rust: writing your own macros
How to write declarative macros in Rust with macro_rules!: matchers, fragment specifiers like expr and ident, repetition with $(...),*, and when to use them.
You've been calling macros since your first program: println!, vec!, format!, assert_eq!. A macro is code that writes code at compile time. macro_rules! lets you write your own declarative macros: you describe input patterns, and what each one expands to.
A first macro
macro_rules! square {
($x:expr) => {
$x * $x
};
}
fn main() {
println!("{}", square!(7));
println!("{}", square!(2 + 3));
}
49
25
- Each rule is
(matcher) => { expansion };. Rules are tried top to bottom; the first that matches is used. $x:exprmatches any expression and names it$x.square!(2 + 3)gives 25, not 11: macros work on syntax trees, not text, so$xstays one expression. (Unlike C's#define.)
Fragment specifiers
| Specifier | Matches |
|---|---|
expr |
an expression: 1 + 2, foo(x), vec![1] |
ident |
an identifier: count, Player |
ty |
a type: u32, Vec<String> |
literal |
a literal: 42, "hi", true |
pat |
a pattern: Some(x), 1..=5 |
block |
a { ... } block |
tt |
a single token tree (the most flexible) |
Repetition
$( ... ),* matches zero or more comma-separated items; + means one or more; ? means optional. Repeat the same group in the expansion to emit code per item:
macro_rules! hashmap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}};
}
let ages = hashmap! { "Ferris" => 7, "Corro" => 3 };
$(,)? accepts an optional trailing comma. The double braces {{ ... }} make the expansion a block expression, so the macro can be used where a value is expected.
Hygiene
Variables created inside a macro (like map above) can't clash with the caller's variables of the same name: macro_rules! is hygienic for local variables. Items (functions, types) are not, so refer to them with full paths ($crate::helper, ::std::collections::HashMap) in macros you export.
When to write a macro
Reach for a function or a generic first: they're easier to read, type-check and debug. Macros earn their place when you need something functions can't do:
- a variable number of arguments (
vec!,println!), - generating repetitive items (
implblocks for many types, enum variants with matching methods), - small domain-specific syntax (
hashmap!{ k => v }, test tables), - code that needs the caller's tokens, like
stringify!(expr)for assertion messages.
#[macro_export] makes a macro available to other crates. cargo expand (an installable tool) shows what macros expand to, which is the best way to debug them.
Errors
Calling a macro with input no rule matches gives errors like no rules expected `,` or unexpected end of macro invocation. Errors inside the expansion point into the macro's body.
Procedural macros
#[derive(Serialize)], #[tokio::main] and #[test]-like attributes are procedural macros: Rust functions, in a separate crate, that receive a token stream and return a new one. They're far more powerful (and more work) than macro_rules!; most developers use them every day but rarely write one.
Example
use std::collections::BTreeMap;
macro_rules! square {
($x:expr) => {
$x * $x
};
}
// Variadic: zero or more key => value pairs, optional trailing comma.
macro_rules! btreemap {
($($key:expr => $value:expr),* $(,)?) => {{
let mut map = BTreeMap::new();
$( map.insert($key, $value); )*
map
}};
}
// Several rules, tried in order.
macro_rules! max_of {
($x:expr) => { $x };
($x:expr, $($rest:expr),+) => {{
let a = $x;
let b = max_of!($($rest),+);
if a > b { a } else { b }
}};
}
// Generating items: one function per identifier.
macro_rules! unit_fns {
($($name:ident => $factor:expr),* $(,)?) => {
$( fn $name(v: f64) -> f64 { v * $factor } )*
};
}
unit_fns! {
km_to_miles => 0.621371,
kg_to_pounds => 2.20462,
}
// stringify! turns the caller's tokens into text.
macro_rules! check {
($cond:expr) => {
println!("{:<22} {}", stringify!($cond), if $cond { "ok" } else { "FAILED" });
};
}
fn main() {
println!("{} {}", square!(7), square!(2 + 3));
let stock = btreemap! { "potion" => 5, "sword" => 1, "arrow" => 40, };
println!("{stock:?}");
println!("max {}", max_of!(3, 17, 9, 12));
println!("{:.2} miles, {:.1} lb", km_to_miles(10.0), kg_to_pounds(5.0));
check!(square!(4) == 16);
check!(stock.len() > 5);
}
Output:
49 25
{"arrow": 40, "potion": 5, "sword": 1}
max 17
6.21 miles, 11.0 lb
square!(4) == 16 ok
stock.len() > 5 FAILED