Macros Basics
10 examples to get you started with Macros - 7 basic and 3 intermediate.
Prerequisites
- Rust 1.97.0 (edition 2024)
- Understanding that macros expand at compile time
Basic Examples
1. Declarative vs Procedural
// declarative: macro_rules!
macro_rules! say_hello {
() => { println!("Hello"); };
}
// procedural: #[derive(Serialize)] implemented by proc macro crate- Declarative macros match token patterns
- Procedural macros are Rust functions on token streams
- Both run at compile time
- Prefer functions/generics when possible
Related: macro_rules!
2. macro_rules! Hello
macro_rules! greet {
($name:expr) => {
println!("Hello, {}", $name);
};
}
fn main() {
greet!("Rust");
}$name:exprcaptures an expression- Expands to plain Rust at call site
- No type checking inside matcher until expansion
- Use
cargo expandto debug output
3. Macro Hygiene
Macros do not accidentally capture local variables from caller (hygienic for macro_rules!).
macro_rules! double {
($x:expr) => { $x * 2 };
}
fn demo() {
let x = 10;
assert_eq!(double!(5), 10); // uses 5, not local x
}- Hygiene reduces surprise name collisions
- Proc macros can break hygiene if generating identifiers carelessly
- Still think about exported crate paths
Related: Debugging Macros
4. Built-in Macros
println!("debug = {:?}", dbg!(value));
todo!();
unreachable!();format!,vec!,panic!are macrosinclude_str!,env!compile-time embedcfg!andconcat!for conditional build
5. When Not to Use Macros
// Prefer generic function:
fn max<T: Ord>(a: T, b: T) -> T {
if a > b { a } else { b }
}- Functions have better errors and IDE support
- Macros for repetitive syntax DSLs
- Derive proc macros for traits
Related: Macros Best Practices
6. Fragment Specifiers
macro_rules! id {
($x:ident) => { stringify!($x) };
}Common specifiers: expr, ident, ty, path, stmt, block, tt.
7. Repetition
macro_rules! vec_str {
($($x:expr),*) => {
vec![$($x.to_string()),*]
};
}$()*repeats pattern$(,)*optional comma separation- Powers
vec!and similar APIs
Related: macro_rules!
Intermediate Examples
8. Derive Macros
#[derive(Debug, Clone, serde::Serialize)]
struct User { id: u64 }- Implemented as proc macros in external crates
syn+quoteparse and generate code
Related: Derive Macros
9. Attribute Macros
#[tokio::main]
async fn main() { }- Attribute proc macros rewrite the item below
- Used by frameworks for boilerplate reduction
Related: Attribute Macros
10. Debugging Expansion
cargo install cargo-expand
cargo expand -p mycrate my_fn- Always expand when macro errors confuse
- Pair with
trace_macros!(unstable) sparingly
Related: Debugging Macros
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.