Can you write this from memory?
Declare a macro named `say_hello` that expands to `println!("Hello!");`.
Macros in Rust generate code at compile time. macro_rules! creates declarative macros using pattern matching. You've used them: println!, vec!, format!.
The syntax is unusual---fragment specifiers like $x:expr, repetition with $(...)*, and match arms that look almost like regular Rust but aren't quite.
This page focuses on macro basics so you can read and write simple macros.
Fragment specifiers tell the macro parser what kind of syntax to expect:
| Specifier | Matches | Example input |
|---|---|---|
expr | Any expression | 1 + 1, foo(), x |
ident | Single identifier | foo, my_var |
ty | Type | i32, Vec<String> |
pat | Pattern | _, Some(x), 1..=5 |
stmt | Statement | let x = 5 |
block | Block expression | { ... } |
item | Item (fn, struct, impl) | fn foo() {} |
path | Type path | std::io::Error |
literal | Literal value | 42, "hello", true |
tt | Any single token tree | Anything (most flexible) |
meta | Attribute content | derive(Debug) |
lifetime | Lifetime | 'a, 'static |
vis | Visibility | pub, pub(crate), (empty) |
tt (token tree) is the escape hatch — it matches any single token or balanced group in brackets. Use it for forwarding arbitrary tokens to other macros.
The most common macro error. Your fragment specifier doesn't match the input:
// WRONG: ident only matches a single identifier
// macro_rules! double {
// ($x:ident) => { $x * 2 };
// }
// double!(1 + 1); // error: no rules expected `1`
// RIGHT: use expr for expressions
macro_rules! double {
($x:expr) => { $x * 2 };
}
let result = double!(1 + 1); // 4
ident matches only a single identifier like x or foo. An expression like 1 + 1 requires expr.
// Simplest macro: no arguments
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
say_hello!();
// Capture an identifier and generate code
macro_rules! create_function {
($name:ident) => {
fn $name() {
println!("Called {}", stringify!($name));
}
};
}
create_function!(foo);
foo(); // prints "Called foo"
Semicolons between arms are required. A trailing semicolon after the last arm is conventional. The pattern-matching syntax inside macro_rules! is inspired by regular pattern matching, though the macro version operates on tokens rather than values.
| Syntax | Matches | Example |
|---|---|---|
$($x:expr),* | Zero or more, comma-separated | 1, 2, 3 or empty |
$($x:expr),+ | One or more, comma-separated | 1, 2, 3 (not empty) |
$($x:expr),* $(,)? | Zero or more with optional trailing comma | 1, 2, 3, |
// Build a Vec from comma-separated expressions
macro_rules! vec_of {
($($x:expr),* $(,)?) => {
{
let mut v = Vec::new();
$(v.push($x);)*
v
}
};
}
let v = vec_of![1, 2, 3];
let v2 = vec_of![1, 2, 3,]; // trailing comma OK
// Generate a function with a captured name and type
macro_rules! make_adder {
($name:ident, $t:ty) => {
fn $name(x: $t, y: $t) -> $t {
x + y
}
};
}
make_adder!(add_i32, i32);
let sum = add_i32(2, 3); // 5
Arms are matched top-to-bottom. Put specific patterns before general ones:
macro_rules! log {
($msg:expr) => {
println!("[LOG] {}", $msg)
};
($fmt:expr, $($arg:tt)*) => {
println!("[LOG] {}", format!($fmt, $($arg)*))
};
}
log!("simple message");
log!("hello {}", "world");
When two arms could both match the same input, the parser cannot decide:
// WRONG: both arms match a single expr
// macro_rules! ambiguous {
// ($x:expr) => { println!("one: {}", $x) };
// ($x:expr, $y:expr) => { println!("two: {} {}", $x, $y) };
// }
// ambiguous!(1, 2); // error: local ambiguity
// RIGHT: add a distinguishing token
macro_rules! log {
(single: $x:expr) => { println!("one: {}", $x) };
(pair: $x:expr, $y:expr) => { println!("two: {} {}", $x, $y) };
}
log!(single: 1);
log!(pair: 1, 2);
The issue: $x:expr in the first arm can match 1, 2 as a single expression containing the comma operator. Adding a keyword prefix or restructuring the arms removes the ambiguity.
Macros in Rust are partially hygienic — variables defined inside the macro expansion don't leak into the caller's scope:
macro_rules! make_var {
() => {
let x = 42; // this x is scoped to the expansion
};
}
let x = 0;
make_var!();
println!("{x}"); // still 0 — the macro's x is separate
However, macros can access items (fn, struct, etc.) from the caller's scope. When passing identifiers to macros, the generated code uses the caller's namespace. The traits and generics guide explains how generics often provide a cleaner alternative to macros when the goal is type-parametric code.
Macro errors point at the call site, not the expansion. Use cargo expand to see generated code:
cargo install cargo-expand
cargo expand # expand all macros in the crate
cargo expand my_module # expand a specific module
For quick debugging, stringify!() shows what tokens were captured:
macro_rules! debug_me {
($($t:tt)*) => {
println!("tokens: {}", stringify!($($t)*));
};
}
debug_me!(hello world 42); // prints: tokens: hello world 42
This page covers declarative macros (macro_rules!). Rust also has procedural macros, which are different:
macro_rules! | Procedural macros | |
|---|---|---|
| Defined with | Pattern matching on tokens | Rust code in a proc-macro crate |
| Examples | vec!, println! | #[derive(Debug)], #[tokio::main] |
| Complexity | Simple to moderate | Arbitrary code generation |
If you're looking for #[derive(...)] traits, the derive traits guide covers which traits can be derived and when to implement manually. If you need attribute macros like #[test] or #[tokio::main], those are procedural macros.
- The Rust Book: Macros — declarative vs procedural overview
- Rust By Example: macro_rules! — practical examples
- The Little Book of Rust Macros — deep dive into macro patterns
- Rust Reference: Macros By Example — formal syntax and fragment specifiers
When to Use Rust Macros: macro_rules!, Declarative Macros
- Use macros to reduce repetitive code patterns.
- Use macros for DSLs (domain-specific languages).
- Use macros when functions cannot express what you need (e.g., variadic args).
- Prefer functions when possible. Macros are harder to debug.
- Use `#[macro_export]` to make macros available outside the module.
Check Your Understanding: Rust Macros: macro_rules!, Declarative Macros
What is the difference between macros and functions in Rust?
Functions operate on values at runtime. Macros operate on syntax at compile time—they see tokens, not values. Macros can be variadic, generate code, and do things impossible with functions. But they are harder to debug and have unusual syntax.
What You'll Practice: Rust Macros: macro_rules!, Declarative Macros
Common Rust Macros: macro_rules!, Declarative Macros Pitfalls
- Symptom: `error: no rules expected the token`. Why: You used the wrong fragment specifier---e.g., `$x:ident` when the caller passes an expression like `1 + 1`. Fix: Match the specifier to the input: `expr` for expressions, `ident` for identifiers, `ty` for types, `tt` for arbitrary tokens.
- Symptom: `error: macro expansion ignores token` or unexpected parse failures. Why: You forgot the semicolon between macro arms. Fix: Put semicolons between arms; a trailing semicolon after the last arm is conventional and keeps diffs cleaner.
- Symptom: Macro compiles but produces wrong output or silently drops values. Why: Macros operate on tokens, not values---they don't evaluate arguments the way functions do, and repeated use of a captured expression evaluates it multiple times. Fix: Bind complex expressions to a `let` variable inside the expansion to evaluate once.
- Symptom: Callers get a syntax error when adding a trailing comma like `my_macro!(1, 2, 3,)`. Why: Your repetition pattern doesn't allow an optional trailing separator. Fix: Add `$(,)?` after the repetition: `$($x:expr),* $(,)?`.
- Symptom: You're writing a macro but struggling with debugging and error messages. Why: Macros are inherently harder to debug than functions---error messages point at the call site, not the expansion. Fix: Prefer functions when possible. When you must use macros, use `cargo expand` to see the generated code and `stringify!()` to inspect captured tokens.
Rust Macros: macro_rules!, Declarative Macros FAQ
What are fragment specifiers?
`$x:expr` means "capture an expression as $x." Other specifiers: `ident` (identifier), `ty` (type), `pat` (pattern), `stmt` (statement), `block`, `item`, `tt` (token tree).
What does $(...)* mean?
Repetition. `$($x:expr),*` matches zero or more comma-separated expressions. Use `+` for one or more. Use `?` for zero or one.
What is tt (token tree)?
`tt` matches any single token or a balanced group in brackets. It is the most flexible fragment specifier, useful for forwarding arbitrary tokens.
Why use macros instead of generics?
Macros work on syntax (can generate new identifiers, types, code). Generics work on types (same code, different types). Use generics when possible; macros when you need code generation.
How do I debug macros?
Use `cargo expand` to see generated code. Start simple and add complexity. Test each pattern individually. Use `stringify!` to see what was captured.
Rust Macros: macro_rules!, Declarative Macros Syntax Quick Reference
macro_rules! hi { () => { println!("hi"); }; }macro_rules! double { ($x:expr) => { $x * 2 }; }macro_rules! make_fn { ($n:ident) => { fn $n() { println!("called"); } }; }($($x:expr),*) => { $($x);* }($($x:expr),* $(,)?)macro_rules! log { () => { println!("empty"); }; ($x:expr) => { println!("{x}", x = $x); }; }stringify!($name)#[macro_export]
macro_rules! my_macro { () => { 42 }; }Rust Macros: macro_rules!, Declarative Macros Sample Exercises
Fill in the syntax to invoke the macro `say_hello`.
!()Fill in the fragment specifier to capture any expression.
:exprFill in the fragment specifier to capture an identifier (function/variable name).
:ident+ 22 more exercises