Macros in Rust
Macros in Rust are a form of metaprogramming — code that writes code.
They run at compile time and can generate repetitive code, create DSLs, and implement
derive functionality. Rust has two main kinds: declarative macros
(macro_rules!) and procedural macros.
Declarative Macros (macro_rules!)
// Simple macro
macro_rules! say_hello {
() => {
println!("Hello from macro!");
};
($name:expr) => {
println!("Hello, {}!", $name);
};
}
// vec!-like macro
macro_rules! my_vec {
() => { Vec::new() };
( $( $x:expr ),+ $(,)? ) => {
{
let mut v = Vec::new();
$( v.push($x); )+
v
}
};
}
// HashMap literal macro
macro_rules! hashmap {
( $( $key:expr => $val:expr ),* $(,)? ) => {
{
let mut map = std::collections::HashMap::new();
$( map.insert($key, $val); )*
map
}
};
}
fn main() {
say_hello!();
say_hello!("Rust");
let v = my_vec![1, 2, 3, 4, 5];
println!("{:?}", v);
let scores = hashmap! {
"Alice" => 100,
"Bob" => 85,
"Charlie" => 92,
};
println!("{:?}", scores);
}
Fragment Types
Macro Fragment Specifiers
$x:expr— Expressions:42,a + b,func()$x:ident— Identifiers:foo,MyStruct$x:ty— Types:i32,Vec<String>$x:pat— Patterns:Some(x),_$x:stmt— Statements:let x = 5;$x:tt— Token tree (most flexible): any single token or group$x:block— Block expression:{ ... }$x:literal— Literal values:42,"hello"
Advanced Declarative Macros
// Generate struct with getters
macro_rules! make_struct {
($name:ident { $( $field:ident : $ty:ty ),* $(,)? }) => {
#[derive(Debug)]
struct $name {
$( $field: $ty, )*
}
impl $name {
$(
fn $field(&self) -> &$ty {
&self.$field
}
)*
}
};
}
make_struct!(Person {
name: String,
age: u32,
email: String,
});
// Implement a trait for multiple types
macro_rules! impl_display {
( $( $t:ty ),* ) => {
$(
impl std::fmt::Display for $t {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
)*
};
}
// Recursive macro for compile-time computation
macro_rules! count {
() => (0usize);
($x:tt $($xs:tt)*) => (1usize + count!($($xs)*));
}
fn main() {
let p = Person {
name: "Alice".into(),
age: 30,
email: "alice@test.com".into(),
};
println!("{}: age {}", p.name(), p.age());
let n = count!(a b c d e);
println!("Count: {n}"); // 5
}
Procedural Macros
// Procedural macros live in their own crate
// Cargo.toml:
// [lib]
// proc-macro = true
//
// [dependencies]
// syn = "2"
// quote = "1"
// proc-macro2 = "1"
// --- Derive macro example ---
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let expanded = quote! {
impl HelloMacro for #name {
fn hello() {
println!("Hello from {}!", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}
// Usage:
// #[derive(HelloMacro)]
// struct Pancakes;
//
// fn main() {
// Pancakes::hello(); // "Hello from Pancakes!"
// }
// --- Attribute macro example ---
// #[proc_macro_attribute]
// pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// // Parse the HTTP method and path from attr
// // Wrap the function in routing logic
// item
// }
//
// #[route(GET, "/api/users")]
// async fn list_users() -> Json> { ... }
Popular Macro Crates
// serde — Serialize/Deserialize derives
#[derive(serde::Serialize, serde::Deserialize)]
struct Config { name: String }
// thiserror — Error type derives
#[derive(thiserror::Error, Debug)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
// strum — Enum utilities
// #[derive(strum::EnumString, strum::Display)]
// enum Color { Red, Blue, Green }
// tokio — Async runtime macro
// #[tokio::main]
// async fn main() { }
// Debugging macros:
// cargo expand # Show expanded macro output
// cargo expand main # Expand specific module
// trace_macros!(true); # Print macro expansion steps
// log_syntax!("hello"); # Print tokens at compile time
Key Takeaways
- ✅
macro_rules!creates pattern-matching macros for code generation - ✅ Fragment specifiers (expr, ident, ty, tt) match different kinds of syntax
- ✅ Procedural macros (derive, attribute, function-like) use syn and quote crates
- ✅ Use
cargo expandto debug and inspect generated macro output - ✅ Prefer functions and generics over macros when possible — macros are a last resort