Metaprogramming in Rust, declarative macros, procedural macros, and derive macros.
macro_rules!)Declarative macros allow pattern matching on token sequences and generating code. They are the most common type of macro in Rust and use the macro_rules! syntax.
// Simple declarative macro
macro_rules! say_hello {
() => {
println!("Hello!");
};
// Macro with arguments
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("You called {:?}", stringify!($func_name));
}
};
fn main() {
say_hello!();
create_function!(my_function);
my_function();
}// Variadic macro with repetition
macro_rules! vec_repeat {
($item:expr; $count:expr) => {
{
let mut v = Vec::new();
for _ in 0..$count {
v.push($item);
}
v
}
};
// Macro with multiple arguments
macro_rules! calculate {
($a:expr, +, $b:expr) => { $a + $b };
($a:expr, -, $b:expr) => { $a - $b };
($a:expr, *, $b:expr) => { $a * $b };
fn main() {
let v = vec_repeat!(5; 3);
println!("Vector: {:?}", v);
println!("10 + 5 = {}", calculate!(10, +, 5));
println!("10 - 5 = {}", calculate!(10, -, 5));
println!("10 * 5 = {}", calculate!(10, *, 5));
}Macros can be invoked with ! using parentheses (), square brackets [], or curly braces {}. The choice is stylistic, vec![] uses brackets, println!() uses parens.
$()*)Repetition patterns allow macros to handle variable numbers of arguments. The $(...)* syntax matches zero or more repetitions, while $(...)+ matches one or more.
// Macro for printing multiple values
macro_rules! print_vals {
($($val:expr),*) => {
$(
println!("{}", $val);
)*
};
// Macro for creating tuples
macro_rules! make_tuple {
($($item:expr),*) => {
($($item),*)
};
fn main() {
print_vals!("Hello", 42, 3.14, true);
let tuple = make_tuple!(1, 2, "three", 4.0);
println!("Tuple: {:?}", tuple);
}// HashMap creation macro
macro_rules! map {
($($key:expr => $value:expr),* $(,)?) => {
{
let mut m = std::collections::HashMap::new();
$(
m.insert($key, $value);
)*
m
}
};
fn main() {
let m = map!{
"name" => "Alice",
"age" => "30",
"city" => "Boston",
};
for (k, v) in &m {
println!("{}: {}", k, v);
}
}The $(,)? pattern at the end of a macro arm allows an optional trailing comma, making the macro more ergonomic to use.
Several patterns appear frequently in Rust code. These include debug printing, custom assertions, and builder-style code generation.
// Debug printing macro
macro_rules! dbg_macro {
($val:expr) => {
match &$val {
tmp => {
eprintln!("[{}:{}] {} = {:#?}",
file!(),
line!(),
stringify!($val),
&tmp);
tmp
}
}
};
// Assert with custom message
macro_rules! assert_msg {
($cond:expr, $msg:expr) => {
assert!($cond, "Custom message: {}", $msg);
};
fn main() {
let x = 42;
let _ = dbg_macro!(x);
assert_msg!(x > 0, "x should be positive");
println!("Assertions passed");
}// Builder pattern macro
macro_rules! builder {
($name:ident { $($field:ident: $typ:ty),* $(,)? }) => {
struct $name {
$(
$field: $typ,
)*
}
impl $name {
fn new() -> Self {
Self {
$(
$field: Default::default(),
)*
}
}
}
};
builder!(Config {
host: String,
port: u16,
debug: bool,
});
fn main() {
let _config = Config::new();
println!("Config created");
}Rust provides file!(), line!(), column!(), and stringify!() as built-in macros, perfect for creating your own debug and logging utilities.
Procedural macros operate on token streams and give more power than declarative macros. They are defined in a separate crate with proc-macro = true in Cargo.toml.
// A procedural macro using the proc_macro crate
// This would be in a separate crate with proc-macro = true
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::LitInt);
let n = input.base10_parse::<i32>().unwrap();
let expanded = quote! {
{
let value = #n;
println!("Value: {}", value);
value
}
};
TokenStream::from(expanded)
}Procedural macros must live in their own crate with proc-macro = true set in Cargo.toml. They depend on the syn crate for parsing and quote for generating tokens.
Derive macros automatically implement traits for structs and enums. Standard derives like Debug, Clone, and PartialEq are built in, and you can create custom ones.
use std::fmt;
// Standard derive macros
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
// Custom trait that could be derived with a proc macro
trait Drawable {
fn draw(&self);
impl Drawable for Point {
fn draw(&self) {
println!("Drawing point at ({}, {})", self.x, self.y);
}
fn main() {
let p = Point { x: 10, y: 20 };
println!("{:?}", p);
p.draw();
}Attribute macros annotate items and transform them. Many are built into Rust and used daily, #[test], #[cfg], #[deprecated], and #[inline] are all attribute macros.
// #[test] - marks functions as tests
#[cfg(test)]
mod tests {
#[test]
fn test_example() {
assert_eq!(2 + 2, 4);
}
// #[deprecated] - marks items as deprecated
#[deprecated(since = "0.2.0", note = "use new_function instead")]
fn old_function() {
println!("This is old");
// #[inline] - hints to compiler to inline function
#[inline]
fn simple_operation(x: i32) -> i32 {
x * 2
fn main() {
let _result = simple_operation(5);
}Frameworks like Actix Web and Rocket use custom attribute macros extensively, #[get("/")], #[tokio::main], and #[actix_web::main] are all attribute procedural macros.
Functions should be your default choice, they are simpler, clearer, and easier to debug. Use macros when you need capabilities that functions cannot provide.
// Use functions when possible - they're simpler and clearer
fn add(a: i32, b: i32) -> i32 {
a + b
// Use macros for:
// 1. Variable argument counts
macro_rules! sum {
($($num:expr),+) => {
0 $( + $num )*
};
// 2. Code generation patterns
macro_rules! implement_ops {
($($op_name:ident -> $op_fn:expr)*) => {
$(
fn $op_name(a: i32, b: i32) -> i32 {
$op_fn(a, b)
}
)*
};
implement_ops! {
multiply -> |a, b| a * b
divide -> |a, b| if b != 0 { a / b } else { 0 }
fn main() {
println!("Sum: {}", sum!(1, 2, 3, 4, 5));
println!("Multiply: {}", multiply(3, 4));
}Use functions for regular logic. Use declarative macros for variable arguments and simple patterns. Use procedural macros for complex code generation and custom derives. Always prefer the simplest tool that gets the job done.