Macros
§Understanding Macros
Macros are a form of metaprogramming that allows you to write code that generates code. They're one of Rust's most powerful features.
Why Use Macros?
- Reduce boilerplate code
- Create domain-specific languages (DSLs)
- Implement compile-time verification
- Generate type-safe code
§Declarative Macros (macro_rules!)
Declarative macros use pattern matching to generate code based on how they're invoked.
Basic Syntax
macro_rules! say_hello {
() => {
println!("Hello!");
};
}
fn main() {
say_hello!(); // Prints: Hello!
}
Macros with Arguments
macro_rules! print_many {
($($arg:expr),*) => {
$(
println!("{}", $arg);
)*
};
}
fn main() {
print_many!(1, "hello", 3.14);
// Prints:
// 1
// hello
// 3.14
}
Pattern Matching in Macros
macro_rules! test {
($x:expr) => {
println!("$x is {}", $x);
};
($x:expr, $y:expr) => {
println!("$x is {}, $y is {}", $x, $y);
};
}
fn main() {
test!(5); // Pattern: ($x:expr)
test!(5, 10); // Pattern: ($x:expr, $y:expr)
}
Repetition
Use $(...)* or $(...)+ for repetition:
macro_rules! vec_init {
($($elem:expr),*) => {
{
let mut vec = Vec::new();
$(
vec.push($elem);
)*
vec
}
};
}
fn main() {
let v = vec_init!(1, 2, 3, 4, 5);
println!("{:?}", v); // [1, 2, 3, 4, 5]
}
§Procedural Macros
Procedural macros take code as input and produce code as output. They're more powerful than declarative macros.
Three Types of Procedural Macros
- Custom Derive -
#[derive(...)] - Attribute Macros -
#[...] - Function-like Macros -
macro_name!(...)
Creating a Custom Derive Macro
First, create a proc-macro crate:
[package]
name = "my_derive"
version = "0.1.0"
[lib]
proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0"
In the macro crate:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(Debug)]
pub fn debug_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let expanded = quote! {
impl std::fmt::Debug for #name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!(#name)).finish()
}
}
};
TokenStream::from(expanded)
}
Attribute Macros
#[proc_macro_attribute]
pub fn my_attribute(args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let name = &input.sig.ident;
let expanded = quote! {
#input
fn #name() {
println!("Executing {}", stringify!(#name));
}
};
TokenStream::from(expanded)
}
§Common Built-in Macros
println! and print!
println!("Hello, {}", "world");
print!("No newline");
assert! Macros
assert!(true);
assert_eq!(2 + 2, 4);
assert_ne!(2 + 2, 5);
panic! Macro
panic!("This is a panic: {}", "error message");
vec! Macro
let v = vec![1, 2, 3, 4];
let v = vec![0; 10]; // 10 zeros
§Building a Custom Macro
Simple Builder Pattern Macro
macro_rules! build {
($name:ident { $($key:ident: $value:expr),* $(,)? }) => {
{
let mut obj = String::new();
$(
obj.push_str(&format!("{}: {}\n", stringify!($key), $value));
)*
obj
}
};
}
fn main() {
let output = build!(config {
host: "localhost",
port: 8080,
debug: true
});
println!("{}", output);
}
DSL: HTML Builder
macro_rules! html {
($tag:ident($($content:expr),*)) => {
{
let mut s = format!("<{}>", stringify!($tag));
$(
s.push_str(&format!("{}", $content));
)*
s.push_str(&format!("</{}>", stringify!($tag)));
s
}
};
}
fn main() {
let page = html!(div(
html!(h1("Title")),
html!(p("Paragraph"))
));
println!("{}", page);
}
§Macro Debugging
Using cargo expand
See what macros expand to:
cargo install cargo-expand
cargo expand
Manual Inspection
// Run with:
// rustc --pretty=expanded file.rs
§Best Practices
- Use macros sparingly - Regular functions are usually clearer
- Document thoroughly - Macro behavior can be surprising
- Keep them simple - Complex macros are hard to debug
- Test extensively - Macro bugs manifest in generated code
- Use meaningful names - Indicate what the macro does
- Provide examples - Show common use cases
§Macro Pitfalls
Variable Shadowing
macro_rules! bad {
($x:expr) => {
let x = $x + 1;
x
};
}
// Calling with a variable named x can shadow it
let x = 5;
bad!(x); // Creates a new x
Hygiene Issues
macro_rules! counter {
() => {
let mut count = 0; // This count is in the macro's scope
count += 1;
count
};
}
§Macros Summary
Macros are the meta-tools of Rust programming. They let you write code that generates code, eliminate boilerplate, and create elegant abstractions. From simple declarative macros to powerful procedural macros, they unlock Rust's full expressive potential.
Use them wisely, and they become your most powerful allies.