Macro Development
Need to write macros in Rust? This guide covers declarative macros (macro_rules!), derive macros, attribute macros, and function-like procedural macros.
Problem: Reducing Code Duplication with Declarative Macros
Scenario
You have repetitive code patterns that can’t be solved with functions.
Solution: Use macro_rules!
Simple example:
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
fn main() {
say_hello!(); // Prints "Hello!"
}With arguments:
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("Function {:?} called", stringify!($func_name));
}
};
}
create_function!(foo);
create_function!(bar);
fn main() {
foo(); // Prints "Function "foo" called"
bar(); // Prints "Function "bar" called"
}Problem: Variadic Arguments
Scenario
Macro needs to accept variable number of arguments.
Solution: Use Repetition Patterns
macro_rules! vec_of_strings {
($($element:expr),*) => {
{
let mut v = Vec::new();
$(
v.push($element.to_string());
)*
v
}
};
}
fn main() {
let v = vec_of_strings!["hello", "world", "from", "rust"];
println!("{:?}", v); // ["hello", "world", "from", "rust"]
}Explanation:
$($element:expr),*: Match zero or more comma-separated expressions$(...)*: Repeat code for each match
Problem: Pattern Matching in Macros
Scenario
Macro should behave differently based on input pattern.
Solution: Use Multiple Arms
macro_rules! calculate {
(eval $e:expr) => {
{
let val: usize = $e;
println!("{} = {}", stringify!{$e}, val);
}
};
(eval $e:expr, $(eval $es:expr),+) => {
{
calculate! { eval $e }
calculate! { $(eval $es),+ }
}
};
}
fn main() {
calculate! {
eval 1 + 2,
eval 3 * 4,
eval (5 - 2) * 3
}
}Problem: Creating Custom Derive Macros
Scenario
You want #[derive(MyTrait)] for your trait.
Solution: Create a Procedural Macro
lib.rs (in a separate my_macro crate):
[lib]
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"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_macro() {
println!("Hello from {}!", stringify!(#name));
}
}
};
TokenStream::from(expanded)
}Usage:
use my_macro::HelloMacro;
pub trait HelloMacro {
fn hello_macro();
}
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro(); // Prints "Hello from Pancakes!"
}Problem: Attribute Macros
Scenario
You want to create custom attributes like #[my_attribute].
Solution: Create an Attribute Procedural Macro
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn log_function(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let fn_name = &input.sig.ident;
let block = &input.block;
let vis = &input.vis;
let sig = &input.sig;
let expanded = quote! {
#vis #sig {
println!("Calling function: {}", stringify!(#fn_name));
let result = (|| #block)();
println!("Function {} returned", stringify!(#fn_name));
result
}
};
TokenStream::from(expanded)
}Usage:
use my_macro::log_function;
#[log_function]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
let greeting = greet("Alice");
println!("{}", greeting);
}Output:
Calling function: greet
Function greet returned
Hello, Alice!Problem: Function-Like Procedural Macros
Scenario
You want a macro that looks like a function call but generates code.
Solution: Create a Function-Like Procedural Macro
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro]
pub fn make_answer(_item: TokenStream) -> TokenStream {
let expanded = quote! {
fn answer() -> u32 {
42
}
};
TokenStream::from(expanded)
}Usage:
use my_macro::make_answer;
make_answer!();
fn main() {
println!("The answer is: {}", answer());
}Problem: Parsing Custom Syntax
Scenario
Your macro needs to parse custom syntax.
Solution: Use syn::parse Module
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse::{Parse, ParseStream}, Ident, Token, LitStr};
struct KeyValue {
key: Ident,
_arrow: Token![=>],
value: LitStr,
}
impl Parse for KeyValue {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(KeyValue {
key: input.parse()?,
_arrow: input.parse()?,
value: input.parse()?,
})
}
}
#[proc_macro]
pub fn key_value(input: TokenStream) -> TokenStream {
let kv = syn::parse_macro_input!(input as KeyValue);
let key = kv.key;
let value = kv.value;
let expanded = quote! {
{
println!("{}: {}", stringify!(#key), #value);
}
};
TokenStream::from(expanded)
}Usage:
use my_macro::key_value;
fn main() {
key_value!(name => "Alice");
// Prints: name: Alice
}Problem: Generating Repetitive Implementations
Scenario
You need to implement a trait for many types.
Solution: Use Declarative Macro
macro_rules! impl_display_for {
($($t:ty),+) => {
$(
impl std::fmt::Display for $t {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
)+
};
}
struct Point { x: i32, y: i32 }
struct Color { r: u8, g: u8, b: u8 }
impl_display_for!(Point, Color);
fn main() {
let p = Point { x: 1, y: 2 };
let c = Color { r: 255, g: 0, b: 0 };
println!("{}", p); // Point { x: 1, y: 2 }
println!("{}", c); // Color { r: 255, g: 0, b: 0 }
}Problem: Debugging Macros
Scenario
Your macro doesn’t work and you can’t see what it expands to.
Solution: Use cargo-expand
cargo install cargo-expand
cargo expandOr check specific macro:
cargo expand --bin my_binary | grep -A 20 "my_macro"Use dbg! in macro_rules!:
macro_rules! debug_macro {
($($t:tt)*) => {
{
eprintln!("Macro input: {}", stringify!($($t)*));
$($t)*
}
};
}Common Macro Patterns
Pattern: Optional Trailing Comma
macro_rules! vec_no_clone {
($($x:expr),* $(,)?) => { // $(,)? allows optional trailing comma
vec![$($x),*]
};
}
fn main() {
let v1 = vec_no_clone![1, 2, 3,]; // Works
let v2 = vec_no_clone![1, 2, 3]; // Also works
}Pattern: Internal Rules
macro_rules! count {
() => (0);
($head:expr) => (1);
($head:expr, $($tail:expr),+) => (1 + count!($($tail),+));
}
fn main() {
assert_eq!(count!(1, 2, 3, 4, 5), 5);
}Pattern: TT Muncher
macro_rules! mixed {
() => {};
(struct $name:ident { $($field:ident: $ty:ty),* } $($rest:tt)*) => {
struct $name {
$($field: $ty),*
}
mixed!($($rest)*);
};
(fn $name:ident() { $($body:tt)* } $($rest:tt)*) => {
fn $name() {
$($body)*
}
mixed!($($rest)*);
};
}
mixed! {
struct Point {
x: i32,
y: i32
}
fn hello() {
println!("Hello!");
}
}Common Pitfalls
Pitfall 1: Hygiene Issues
Problem: Variables from macro conflict with surrounding code.
Solution: Use unique identifiers or gensym.
// Bad - might conflict
macro_rules! bad_macro {
() => {
let x = 42;
};
}
// Better - unlikely to conflict
macro_rules! better_macro {
() => {
let __macro_x_42 = 42;
};
}Pitfall 2: Not Considering Expansion Context
Problem: Macro assumes certain items are in scope.
Solution: Use fully qualified paths.
macro_rules! print_vec {
($vec:expr) => {
{
use std::fmt::Write as _; // Explicit import
let mut s = String::new();
write!(&mut s, "{:?}", $vec).unwrap();
println!("{}", s);
}
};
}Pitfall 3: Overly Complex Macros
Problem: Macro becomes unmaintainable.
Solution: Consider if a function, trait, or procedural macro would be clearer.
Related Resources
- Tutorials: Advanced - Advanced macro patterns
- Cookbook - Macro recipes
- Best Practices - When to use macros
- Resources - Macro development tools
Write powerful macros to reduce boilerplate and generate code!