Reading — step 1 of 4
Learn
~1 min readMacros and Unsafe
Rust has TWO kinds of macros:
- Declarative (
macro_rules!) — pattern matching on token trees, defined in your code - Procedural (proc macros) — Rust functions that take and produce token streams, in a separate crate
This lesson is about declarative macros — the simpler kind.
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
fn main() {
say_hello!(); // expands to println!("Hello!");
}
Pattern matching:
macro_rules! greet {
() => {
println!("Hello!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
($greeting:expr, $name:expr) => {
println!("{}, {}!", $greeting, $name);
};
}
greet!(); // "Hello!"
greet!("Ada"); // "Hello, Ada!"
greet!("Hi", "World"); // "Hi, World!"
Fragment specifiers describe what kind of token tree to match:
expr— an expressionident— identifierty— typepat— patternstmt— statementblock—{ ... }blockpath—::path::like::thistt— single token tree (catch-all)literal— literal value
Variadic with * and +:
macro_rules! vec_of_doubled {
($($x:expr),*) => {
vec![$($x * 2),*]
};
}
let v = vec_of_doubled!(1, 2, 3); // vec![2, 4, 6]
The standard vec! macro is implemented this way — that's why vec![1, 2, 3] works.
When to use macros:
- Reduce repetition that traits/generics can't (e.g., variable-arity)
- DSLs (small embedded languages)
- Compile-time guarantees (e.g.,
format_args!checks format strings)
Most Rust code has zero declarative macros. Reach for them when generics + traits aren't enough.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…