Macros¶
Rust macros generate code at compile time. Two kinds: declarative macros (macro_rules!) use pattern matching on syntax, procedural macros transform token streams via Rust code. Macros enable variadic functions, custom derive implementations, and zero-cost DSLs. Prefer regular functions when possible - macros are harder to debug.
Key Facts¶
macro_rules!= declarative, pattern-matching macro (most common)- Procedural macros: derive (
#[derive(MyMacro)]), attribute (#[my_attr]), function-like (my_macro!(...)) - Procedural macros live in a separate crate with
proc-macro = true - Fragment specifiers:
expr,ident,ty,pat,stmt,block,item,tt,literal - Repetitions:
$(...)*(0+),$(...)+(1+),$(...)?(0 or 1) - Standard macros:
vec![],println!(),format!(),assert!(),dbg!()
Patterns¶
Declarative Macros (macro_rules!)¶
macro_rules! vec {
() => { Vec::new() };
($($x:expr),+ $(,)?) => {
{
let mut temp = Vec::new();
$(temp.push($x);)+
temp
}
};
}
// Usage
let v = vec![1, 2, 3];
let empty: Vec<i32> = vec![];
Fragment Specifiers¶
macro_rules! create_fn {
($name:ident, $body:block) => {
fn $name() $body
};
}
macro_rules! calculate {
($a:expr, $op:tt, $b:expr) => {
$a $op $b
};
}
create_fn!(greet, { println!("Hello!"); });
let result = calculate!(2, +, 3); // 5
Procedural Macros (Derive)¶
// In a separate proc-macro crate
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(MyDerive)]
pub fn my_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl MyTrait for #name {
fn describe(&self) -> String {
format!("I am a {}", stringify!(#name))
}
}
};
TokenStream::from(expanded)
}
// Usage
#[derive(MyDerive)]
struct Widget;
Attribute Macros¶
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// attr = "GET, /api/users"
// item = the annotated function
// return modified function
item
}
// Usage
#[route(GET, "/api/users")]
fn list_users() { /* ... */ }
Macro vs Function¶
| Use macros when | Use functions when |
|---|---|
| Variadic arguments needed | Fixed arguments |
| Types/identifiers as params | Values as params |
| Compile-time code generation | Runtime computation |
| Custom derive/attribute | Regular behavior |
| DSL syntax | Standard Rust |
Gotchas¶
- Macro errors are harder to debug - use
cargo expandto see generated code - Declarative macros match patterns greedily - order of arms matters
- Procedural macros require a separate crate (
proc-macro = truein Cargo.toml) $x:tt(token tree) is the most flexible but least type-safe fragment specifier- Macros cannot access runtime values - they only see syntax tokens at compile time
See Also¶
- [[traits]] - derive macros auto-implement traits
- [[modules-and-visibility]] - macro export and scoping rules
- [[rust-tooling]] - cargo expand for macro debugging