macro_rules! patterns and repetition
Declarative macros, defined using macro_rules!, are Rust's original macro system for metaprogramming through pattern matching. Unlike procedural macros that operate on token streams, declarative macros work by matching input patterns against rules and expanding into replacement code at compile time.
Think of declarative macros as hygienic, type-aware code templates with pattern matching superpowers. They allow you to write code that writes code, reducing boilerplate and creating domain-specific languages while maintaining Rust's safety guarantees.
// A simple declarative macro
macro_rules! say_hello {
() => {
println!("Hello, macro world!")
};
}
// Multiple patterns
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("You called {:?}", stringify!($func_name));
}
};
}
say_hello!(); // Expands to: println!("Hello, macro world!")
create_function!(foo); // Creates a function named foo
foo(); // "You called \"foo\""
Key characteristics:
$(...)* patternsCreating ergonomic collection initialization macros is a common use case. Here's a production-quality hashmap macro that handles multiple syntaxes:
/// Create a HashMap from key-value pairs
///
/// # Examples
///
/// let map = hashmap! {
/// "key1" => 42,
/// "key2" => 7,
/// };
///
/// let empty: HashMap
///
#[macro_export]
macro_rules! hashmap {
// Empty map
() => {
::std::collections::HashMap::new()
};
// Map with capacity hint (advanced usage)
(@capacity $cap:expr) => {
::std::collections::HashMap::with_capacity($cap)
};
// Main pattern: key => value pairs with optional trailing comma
($($key:expr => $value:expr),+ $(,)?) => {{
let mut map = ::std::collections::HashMap::new();
$(
map.insert($key, $value);
)+
map
}};
// With capacity hint
(@capacity $cap:expr, $($key:expr => $value:expr),+ $(,)?) => {{
let mut map = ::std::collections::HashMap::with_capacity($cap);
$(
map.insert($key, $value);
)+
map
}};
}
// Real-world usage in configuration management
use std::collections::HashMap;
struct AppConfig {
settings: HashMap<String, String>,
ports: HashMap<String, u16>,
}
impl AppConfig {
fn new() -> Self {
Self {
settings: hashmap! {
"env".to_string() => "production".to_string(),
"region".to_string() => "us-west-2".to_string(),
"log_level".to_string() => "info".to_string(),
},
ports: hashmap! {
"http".to_string() => 8080,
"https".to_string() => 8443,
"metrics".to_string() => 9090,
},
}
}
}
// Advanced: Type-converting variant
macro_rules! hashmap_into {
($($key:expr => $value:expr),+ $(,)?) => {{
let mut map = ::std::collections::HashMap::new();
$(
map.insert($key.into(), $value.into());
)+
map
}};
}
// Now you can use string literals directly
let map: HashMap<String, String> = hashmap_into! {
"name" => "Alice",
"role" => "admin",
};
Why this matters in production:
insert() callsA compile-time checked SQL DSL that prevents common SQL injection vulnerabilities:
/// SQL query builder with compile-time validation
///
/// This macro provides type-safe SQL query construction with
/// parameter binding to prevent SQL injection attacks.
#[macro_export]
macro_rules! sql {
// SELECT query with WHERE clause
(SELECT $($field:ident),+ FROM $table:ident WHERE $($condition:tt)+) => {{
let fields = vec![$(stringify!($field)),+].join(", ");
let table = stringify!($table);
let where_clause = sql!(@where $($condition)+);
SqlQuery {
query: format!("SELECT {} FROM {} WHERE {}", fields, table, where_clause),
params: vec![],
}
}};
// SELECT query without WHERE
(SELECT $($field:ident),+ FROM $table:ident) => {{
let fields = vec![$(stringify!($field)),+].join(", ");
let table = stringify!($table);
SqlQuery {
query: format!("SELECT {} FROM {}", fields, table),
params: vec![],
}
}};
// INSERT query
(INSERT INTO $table:ident ($($field:ident),+) VALUES ($($placeholder:tt),+)) => {{
let table = stringify!($table);
let fields = vec![$(stringify!($field)),+].join(", ");
let placeholders = vec![$(stringify!($placeholder)),+].join(", ");
SqlQuery {
query: format!("INSERT INTO {} ({}) VALUES ({})",
table, fields, placeholders),
params: vec![],
}
}};
// Internal rule for WHERE clause processing
(@where $field:ident = $value:tt) => {
format!("{} = ?", stringify!($field))
};
(@where $field:ident > $value:tt) => {
format!("{} > ?", stringify!($field))
};
(@where $lhs:tt AND $($rhs:tt)+) => {
format!("{} AND {}", sql!(@where $lhs), sql!(@where $($rhs)+))
};
}
#[derive(Debug)]
struct SqlQuery {
query: String,
params: Vec<String>,
}
impl SqlQuery {
fn bind<T: ToString>(mut self, value: T) -> Self {
self.params.push(value.to_string());
self
}
}
// Real-world usage in a user service
struct UserRepository;
impl UserRepository {
fn find_active_users_by_role(&self, role: &str) -> SqlQuery {
sql!(SELECT id, name, email FROM users WHERE role = ? AND active = ?)
.bind(role)
.bind(true)
}
fn create_user(&self, name: &str, email: &str, role: &str) -> SqlQuery {
sql!(INSERT INTO users (name, email, role, created_at)
VALUES (?, ?, ?, ?))
.bind(name)
.bind(email)
.bind(role)
.bind(chrono::Utc::now().to_rfc3339())
}
fn find_high_score_users(&self, min_score: i32) -> SqlQuery {
sql!(SELECT id, name, score FROM users WHERE score > ?)
.bind(min_score)
}
}
// Advanced: Type-safe column selection
macro_rules! sql_typed {
(SELECT $($field:ident : $type:ty),+ FROM $table:ident) => {
{
#[derive(Debug)]
struct Query {
$($field: Option<$type>,)+
}
impl Query {
fn columns() -> Vec<&'static str> {
vec![$(stringify!($field)),+]
}
fn sql() -> String {
format!("SELECT {} FROM {}",
Self::columns().join(", "),
stringify!($table))
}
}
Query { $($field: None,)+ }
}
};
}
// Usage with type safety
let query = sql_typed!(SELECT
id: i64,
name: String,
score: i32
FROM users);
println!("SQL: {}", query.sql());
Production value:
Custom assertion macros with superior error messages and debugging context:
/// Assert that collections contain expected elements
#[macro_export]
macro_rules! assert_contains {
($collection:expr, $item:expr) => {{
let collection = &$collection;
let item = &$item;
if !collection.iter().any(|x| x == item) {
panic!(
r#"assertion failed: collection does not contain item
collection: {:?}
missing item: {:?}
location: {}:{}"#,
collection,
item,
file!(),
line!()
);
}
}};
($collection:expr, $item:expr, $($arg:tt)+) => {{
let collection = &$collection;
let item = &$item;
if !collection.iter().any(|x| x == item) {
panic!(
r#"assertion failed: {}
collection: {:?}
missing item: {:?}
location: {}:{}"#,
format_args!($($arg)+),
collection,
item,
file!(),
line!()
);
}
}};
}
/// Assert equality with sorted collections
#[macro_export]
macro_rules! assert_eq_sorted {
($left:expr, $right:expr) => {{
let mut left = $left.clone();
let mut right = $right.clone();
left.sort();
right.sort();
if left != right {
panic!(
r#"assertion failed: `(left == right)` (after sorting)
left (sorted): {:?}
right (sorted): {:?}
left (original): {:?}
right (original): {:?}
location: {}:{}"#,
left,
right,
$left,
$right,
file!(),
line!()
);
}
}};
}
/// Assert with detailed context capture
#[macro_export]
macro_rules! assert_with_context {
($condition:expr, context = { $($key:ident : $value:expr),+ $(,)? }) => {{
if !$condition {
panic!(
r#"assertion failed: {}
context:
{}
location: {}:{}"#,
stringify!($condition),
vec![
$(format!(" {} = {:?}", stringify!($key), $value)),+
]
.join("\n"),
file!(),
line!()
);
}
}};
}
// Real-world usage in integration tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation() {
let users = vec!["alice", "bob", "charlie"];
assert_contains!(users, "alice");
assert_contains!(users, "bob", "Bob should be in the initial user list");
// This would fail with a detailed message
// assert_contains!(users, "dave", "Dave was supposed to be added");
}
#[test]
fn test_query_results() {
let expected = vec![1, 2, 3, 4, 5];
let actual = vec![5, 3, 1, 4, 2]; // Order doesn't matter
assert_eq_sorted!(expected, actual);
}
#[test]
fn test_payment_processing() {
let balance = 100;
let charge = 25;
let min_balance = 50;
assert_with_context!(
balance - charge >= min_balance,
context = {
balance: balance,
charge: charge,
min_balance: min_balance,
remaining: balance - charge,
}
);
}
}
/// Performance-focused assertion that compiles out in release builds
#[macro_export]
macro_rules! debug_assert_valid {
($obj:expr) => {
#[cfg(debug_assertions)]
{
if let Err(e) = $obj.validate() {
panic!(
"validation failed: {:?}\n object: {:?}\n location: {}:{}",
e,
$obj,
file!(),
line!()
);
}
}
};
}
trait Validate {
fn validate(&self) -> Result<(), String>;
}
struct Transaction {
amount: i64,
from: String,
to: String,
}
impl Validate for Transaction {
fn validate(&self) -> Result<(), String> {
if self.amount <= 0 {
return Err("amount must be positive".to_string());
}
if self.from == self.to {
return Err("cannot transfer to same account".to_string());
}
Ok(())
}
}
fn process_transaction(tx: Transaction) {
debug_assert_valid!(tx);
// Process transaction...
}
Production benefits:
A zero-cost logging macro with structured fields and compile-time level filtering:
/// Structured logging with compile-time optimizations
#[macro_export]
macro_rules! log_event {
// Log with structured fields
($level:ident, $message:expr, { $($key:ident = $value:expr),+ $(,)? }) => {{
if log::log_enabled!(log::Level::$level) {
log::log!(
log::Level::$level,
"{}{}",
$message,
format_args!(
" | {}",
vec![
$(format!("{}={:?}", stringify!($key), $value)),+
]
.join(" ")
)
);
}
}};
// Log with message only
($level:ident, $message:expr) => {{
log::log!(log::Level::$level, "{}", $message);
}};
}
/// Conditional compilation based on log level
#[macro_export]
macro_rules! trace_with_timing {
($($arg:tt)*) => {
#[cfg(feature = "trace-logging")]
{
let _timer = $crate::metrics::FunctionTimer::new(
function_name!(),
file!(),
line!()
);
log_event!(Trace, $($arg)*);
}
};
}
/// Specialized error logging with context
#[macro_export]
macro_rules! log_error {
($err:expr, context = { $($key:ident = $value:expr),+ $(,)? }) => {{
log_event!(Error,
format!("Error: {:?}", $err),
{
error_type = std::any::type_name_of_val(&$err),
$($key = $value,)+
file = file!(),
line = line!(),
}
);
}};
}
// Real-world usage in a payment service
mod payment_service {
use std::time::Instant;
pub struct PaymentProcessor;
impl PaymentProcessor {
pub fn process_payment(
&self,
user_id: u64,
amount: i64,
currency: &str,
) -> Result<String, PaymentError> {
let start = Instant::now();
log_event!(Info, "Processing payment", {
user_id = user_id,
amount = amount,
currency = currency,
});
// Simulate payment processing
if amount <= 0 {
let err = PaymentError::InvalidAmount(amount);
log_error!(err, context = {
user_id = user_id,
amount = amount,
currency = currency,
});
return Err(err);
}
if amount > 1_000_000 {
log_event!(Warn, "High-value transaction", {
user_id = user_id,
amount = amount,
currency = currency,
requires_approval = true,
});
}
let duration = start.elapsed();
log_event!(Info, "Payment processed successfully", {
user_id = user_id,
amount = amount,
currency = currency,
duration_ms = duration.as_millis(),
transaction_id = "txn_123456",
});
trace_with_timing!("Payment trace complete", {
details = "Full payment pipeline executed",
});
Ok("txn_123456".to_string())
}
}
#[derive(Debug)]
pub enum PaymentError {
InvalidAmount(i64),
InsufficientFunds,
NetworkError,
}
}
/// Performance: Zero-cost when logging is disabled
macro_rules! perf_log {
($level:ident, $($arg:tt)*) => {
#[cfg(feature = "performance-logging")]
log_event!($level, $($arg)*);
};
}
// Usage in hot paths
fn hot_path_function(iterations: usize) {
for i in 0..iterations {
// This compiles to nothing if performance-logging is disabled
perf_log!(Trace, "Iteration", { i = i });
// ... actual work ...
}
}
Production advantages:
Type-safe bit manipulation for flags, permissions, and protocol states:
/// Generate type-safe bitflags with full trait support
#[macro_export]
macro_rules! bitflags {
(
$(#[$outer:meta])*
$vis:vis struct $name:ident: $type:ty {
$(
$(#[$inner:meta])*
const $flag:ident = $value:expr;
)+
}
) => {
$(#[$outer])*
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
$vis struct $name {
bits: $type,
}
impl $name {
$(
$(#[$inner])*
pub const $flag: Self = Self { bits: $value };
)+
pub const fn empty() -> Self {
Self { bits: 0 }
}
pub const fn all() -> Self {
Self {
bits: $(Self::$flag.bits)|+
}
}
pub const fn from_bits(bits: $type) -> Option<Self> {
let all_bits = Self::all().bits;
if bits & !all_bits == 0 {
Some(Self { bits })
} else {
None
}
}
pub const fn from_bits_truncate(bits: $type) -> Self {
Self {
bits: bits & Self::all().bits,
}
}
pub const fn bits(&self) -> $type {
self.bits
}
pub const fn contains(&self, other: Self) -> bool {
(self.bits & other.bits) == other.bits
}
pub const fn is_empty(&self) -> bool {
self.bits == 0
}
pub const fn is_all(&self) -> bool {
self.bits == Self::all().bits
}
pub fn insert(&mut self, other: Self) {
self.bits |= other.bits;
}
pub fn remove(&mut self, other: Self) {
self.bits &= !other.bits;
}
pub fn toggle(&mut self, other: Self) {
self.bits ^= other.bits;
}
pub fn set(&mut self, other: Self, value: bool) {
if value {
self.insert(other);
} else {
self.remove(other);
}
}
}
impl ::std::ops::BitOr for $name {
type Output = Self;
fn bitor(self, other: Self) -> Self {
Self {
bits: self.bits | other.bits,
}
}
}
impl ::std::ops::BitAnd for $name {
type Output = Self;
fn bitand(self, other: Self) -> Self {
Self {
bits: self.bits & other.bits,
}
}
}
impl ::std::ops::BitXor for $name {
type Output = Self;
fn bitxor(self, other: Self) -> Self {
Self {
bits: self.bits ^ other.bits,
}
}
}
impl ::std::ops::Not for $name {
type Output = Self;
fn not(self) -> Self {
Self {
bits: !self.bits & Self::all().bits,
}
}
}
impl ::std::fmt::Debug for $name {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
let mut flags = Vec::new();
$(
if self.contains(Self::$flag) {
flags.push(stringify!($flag));
}
)+
write!(f, "{}({:#x}): [{}]",
stringify!($name),
self.bits,
flags.join(" | "))
}
}
};
}
// Real-world usage: File permissions
bitflags! {
/// UNIX-style file permissions
pub struct FilePermissions: u32 {
const READ = 0b0000_0100;
const WRITE = 0b0000_0010;
const EXECUTE = 0b0000_0001;
const USER_READ = 0b0100_0000_0000;
const USER_WRITE = 0b0010_0000_0000;
const USER_EXECUTE = 0b0001_0000_0000;
const GROUP_READ = 0b0000_0100_0000;
const GROUP_WRITE = 0b0000_0010_0000;
const GROUP_EXECUTE = 0b0000_0001_0000;
const OTHER_READ = 0b0000_0000_0100;
const OTHER_WRITE = 0b0000_0000_0010;
const OTHER_EXECUTE = 0b0000_0000_0001;
}
}
// Real-world usage: Network protocol flags
bitflags! {
/// TCP flags for packet processing
pub struct TcpFlags: u8 {
const FIN = 0b0000_0001;
const SYN = 0b0000_0010;
const RST = 0b0000_0100;
const PSH = 0b0000_1000;
const ACK = 0b0001_0000;
const URG = 0b0010_0000;
const ECE = 0b0100_0000;
const CWR = 0b1000_0000;
}
}
struct FileSystem;
impl FileSystem {
fn check_access(&self, perms: FilePermissions, user_type: UserType) -> bool {
match user_type {
UserType::Owner => {
perms.contains(FilePermissions::USER_READ)
}
UserType::Group => {
perms.contains(FilePermissions::GROUP_READ)
}
UserType::Other => {
perms.contains(FilePermissions::OTHER_READ)
}
}
}
fn set_executable(&self, perms: &mut FilePermissions) {
perms.insert(
FilePermissions::USER_EXECUTE
| FilePermissions::GROUP_EXECUTE
| FilePermissions::OTHER_EXECUTE,
);
}
}
enum UserType {
Owner,
Group,
Other,
}
struct TcpPacketHandler;
impl TcpPacketHandler {
fn handle_packet(&self, flags: TcpFlags) -> PacketAction {
if flags.contains(TcpFlags::SYN) && !flags.contains(TcpFlags::ACK) {
PacketAction::InitiateConnection
} else if flags.contains(TcpFlags::SYN | TcpFlags::ACK) {
PacketAction::AcknowledgeConnection
} else if flags.contains(TcpFlags::FIN) {
PacketAction::CloseConnection
} else if flags.contains(TcpFlags::RST) {
PacketAction::ResetConnection
} else {
PacketAction::ProcessData
}
}
fn create_syn_ack(&self) -> TcpFlags {
TcpFlags::SYN | TcpFlags::ACK
}
}
enum PacketAction {
InitiateConnection,
AcknowledgeConnection,
CloseConnection,
ResetConnection,
ProcessData,
}
// Advanced: Enum generation with conversion
macro_rules! generate_enum {
(
$(#[$meta:meta])*
$vis:vis enum $name:ident {
$(
$(#[$variant_meta:meta])*
$variant:ident = $value:expr
),+ $(,)?
}
) => {
$(#[$meta])*
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
$vis enum $name {
$(
$(#[$variant_meta])*
$variant = $value
),+
}
impl $name {
pub fn from_u32(value: u32) -> Option<Self> {
match value {
$($value => Some(Self::$variant),)+
_ => None,
}
}
pub fn to_u32(self) -> u32 {
self as u32
}
pub fn all() -> &'static [Self] {
&[$(Self::$variant),+]
}
}
impl ::std::convert::From<$name> for u32 {
fn from(value: $name) -> u32 {
value.to_u32()
}
}
impl ::std::convert::TryFrom<u32> for $name {
type Error = ();
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::from_u32(value).ok_or(())
}
}
};
}
generate_enum! {
/// HTTP status codes
pub enum HttpStatus {
Ok = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500,
}
}
fn handle_response(status: u32) {
match HttpStatus::try_from(status) {
Ok(HttpStatus::Ok) => println!("Success!"),
Ok(HttpStatus::NotFound) => println!("Resource not found"),
Ok(status) => println!("Status: {:?}", status),
Err(_) => println!("Unknown status code: {}", status),
}
}
Production value:
The basic structure of a declarative macro:
macro_rules! macro_name {
// Rule 1: pattern => transcriber
(pattern1) => {
// Code to generate
};
// Rule 2: different pattern
(pattern2) => {
// Different code
};
// Rule N: can have many patterns
(pattern3) => {
// More code
};
}
Pattern matchers can use three delimiter types:
() - Parentheses (most common)[] - Brackets{} - Bracesmacro_rules! delimiters {
// All three are valid and distinct patterns
(($x:expr)) => { println!("parens: {}", $x) };
([$x:expr]) => { println!("brackets: {}", $x) };
({$x:expr}) => { println!("braces: {}", $x) };
}
delimiters!((42)); // "parens: 42"
delimiters!([42]); // "brackets: 42"
delimiters!({42}); // "braces: 42"
Fragment specifiers define what kind of Rust syntax can be matched:
macro_rules! fragment_examples {
// expr - any expression
($x:expr) => {
println!("Expression: {}", $x)
};
// ty - any type
($t:ty) => {
std::mem::size_of::<$t>()
};
// ident - identifier (variable/function name)
($name:ident) => {
let $name = 42;
};
// path - path like std::collections::HashMap
($p:path) => {
$p::new()
};
// stmt - statement
($s:stmt) => {
$s
};
// block - code block {}
($b:block) => {
$b
};
// item - item like fn, struct, impl
($i:item) => {
$i
};
// pat - pattern for matching
($p:pat) => {
match value {
$p => true,
_ => false,
}
};
// tt - token tree (any single token or delimited group)
($t:tt) => {
// Most flexible, matches almost anything
};
// meta - attribute contents
($m:meta) => {
#[$m]
};
// lifetime - lifetime parameter
($l:lifetime) => {
struct Foo<$l>(&$l str);
};
// vis - visibility modifier
($v:vis) => {
$v struct Bar;
};
// literal - literal value
($lit:literal) => {
const VALUE: _ = $lit;
};
}
// Real-world example: Builder pattern generator
macro_rules! builder {
(
struct $name:ident {
$(
$field:ident : $type:ty
),* $(,)?
}
) => {
pub struct $name {
$(pub $field: $type,)*
}
paste::paste! {
pub struct [<$name Builder>] {
$($field: Option<$type>,)*
}
impl [<$name Builder>] {
pub fn new() -> Self {
Self {
$($field: None,)*
}
}
$(
pub fn $field(mut self, value: $type) -> Self {
self.$field = Some(value);
self
}
)*
pub fn build(self) -> Result<$name, &'static str> {
Ok($name {
$(
$field: self.$field
.ok_or(concat!("Missing field: ", stringify!($field)))?,
)*
})
}
}
}
};
}
Repetition allows matching variable-length inputs:
// Basic repetition syntax: $( pattern separator ) quantifier
macro_rules! repetition_examples {
// Zero or more (*)
($($x:expr),*) => {
vec![$($x),*]
};
// One or more (+)
($($x:expr),+) => {
vec![$($x),+]
};
// Zero or one (?)
($x:expr $(, $y:expr)?) => {
// $y is optional
};
}
// Multiple repetitions
macro_rules! create_struct {
(
$name:ident {
$($field:ident : $type:ty),* $(,)?
}
) => {
struct $name {
$($field: $type,)*
}
};
}
// Nested repetitions
macro_rules! matrix {
(
$(
[ $($value:expr),* ]
),* $(,)?
) => {
vec![
$(
vec![$($value),*]
),*
]
};
}
let m = matrix![
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
Real-world pattern: Optional trailing comma
macro_rules! trailing_comma {
// Accept with or without trailing comma
($($item:expr),* $(,)?) => {
vec![$($item),*]
};
}
trailing_comma![1, 2, 3]; // Valid
trailing_comma![1, 2, 3,]; // Also valid
Hygiene prevents variables in macros from accidentally capturing variables in the calling scope:
// Hygienic: Variables in macros don't leak
macro_rules! hygienic {
() => {
let x = 42; // This 'x' is scoped to the macro expansion
};
}
let x = 1;
hygienic!();
println!("{}", x); // Still prints 1, not 42
// Breaking hygiene intentionally (rarely needed)
macro_rules! intentional_capture {
($var:ident) => {
let $var = 42; // Uses the identifier from the caller
};
}
intentional_capture!(my_var);
println!("{}", my_var); // Prints 42
Hygiene spans example:
macro_rules! file_location {
() => {
(file!(), line!(), column!())
};
}
// file!(), line!(), column!() are resolved at call site
let loc = file_location!();
println!("Macro called at {:?}", loc);
The internal rules pattern uses private helper rules to break down complex logic:
macro_rules! complex_macro {
// Public entry points
(parse $input:expr) => {
complex_macro!(@internal parse, $input, [])
};
// Internal helper rules (prefixed with @)
(@internal parse, $input:expr, [$($acc:tt)*]) => {
complex_macro!(@process [$($acc)*], $input)
};
(@process [$($result:tt)*], $remaining:expr) => {
// Final processing
vec![$($result)*]
};
}
// Real-world: Recursive macro for type-level computation
macro_rules! count {
// Base case
() => { 0 };
// Internal rule for recursion
($_:tt $($rest:tt)*) => {
1 + count!($($rest)*)
};
}
const LEN: usize = count!(a b c d e); // Computes 5 at compile time
Advanced internal rules for DSL parsing:
macro_rules! state_machine {
// Public API
(
states { $($state:ident),* $(,)? }
transitions {
$($from:ident -> $to:ident on $event:ident),* $(,)?
}
) => {
state_machine! {
@generate_states [$($state)*]
@generate_transitions [$(($from, $to, $event))*]
}
};
// Internal: Generate state enum
(@generate_states [$($state:ident)*]) => {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum State {
$($state,)*
}
};
// Internal: Generate transition function
(@generate_transitions [$(($from:ident, $to:ident, $event:ident))*]) => {
impl State {
fn transition(self, event: Event) -> Option<Self> {
match (self, event) {
$((State::$from, Event::$event) => Some(State::$to),)*
_ => None,
}
}
}
};
}
#[derive(Debug, Copy, Clone)]
enum Event {
Start,
Stop,
Pause,
Resume,
}
state_machine! {
states { Idle, Running, Paused }
transitions {
Idle -> Running on Start,
Running -> Paused on Pause,
Paused -> Running on Resume,
Running -> Idle on Stop,
}
}
Macros can call themselves to process variable-length inputs:
// Classic example: Counting at compile time
macro_rules! count_exprs {
() => { 0 };
($head:expr) => { 1 };
($head:expr, $($tail:expr),*) => {
1 + count_exprs!($($tail),*)
};
}
const COUNT: usize = count_exprs!(1, 2, 3, 4, 5); // 5
// Real-world: Compile-time type list operations
macro_rules! type_list {
// Find the size of the largest type
(@max_size) => { 0 };
(@max_size $head:ty) => {
std::mem::size_of::<$head>()
};
(@max_size $head:ty, $($tail:ty),+) => {{
let head_size = std::mem::size_of::<$head>();
let tail_size = type_list!(@max_size $($tail),+);
if head_size > tail_size { head_size } else { tail_size }
}};
// Public API
(max_size_of [ $($ty:ty),+ $(,)? ]) => {
type_list!(@max_size $($ty),+)
};
}
const MAX: usize = type_list!(max_size_of[u8, u16, u32, u64, u128]); // 16
Advanced: TT muncher pattern
macro_rules! parse_and_transform {
// Base case: no more input
(@munch [] -> [$($output:tt)*]) => {
vec![$($output)*]
};
// Match and transform number literal
(@munch [$num:literal $($rest:tt)*] -> [$($output:tt)*]) => {
parse_and_transform!(
@munch [$($rest)*] -> [$($output)* $num * 2,]
)
};
// Match and transform identifier
(@munch [$id:ident $($rest:tt)*] -> [$($output:tt)*]) => {
parse_and_transform!(
@munch [$($rest)*] -> [$($output)* stringify!($id),]
)
};
// Public entry point
($($input:tt)*) => {
parse_and_transform!(@munch [$($input)*] -> [])
};
}
let result = parse_and_transform!(42 foo 10 bar);
// Doubles numbers, stringifies identifiers
Use cargo-expand to see what your macros expand to:
# Install cargo-expand
cargo install cargo-expand
# Expand macros in a specific function
cargo expand ::module::function_name
# Expand all macros in a module
cargo expand module_name
# Expand with colors (easier to read)
cargo expand --color always
Example debug workflow:
// Your macro
macro_rules! debug_me {
($x:expr) => {
println!("Value: {}", $x);
println!("Type: {}", std::any::type_name_of_val(&$x));
};
}
fn test() {
debug_me!(42 + 10);
}
// Run: cargo expand ::test
// Output shows:
// fn test() {
// {
// ::std::io::_print(::core::fmt::Arguments::new_v1(
// &["Value: ", "\n"],
// &[::core::fmt::ArgumentV1::new_display(&42 + 10)],
// ));
// };
// {
// ::std::io::_print(::core::fmt::Arguments::new_v1(
// &["Type: ", "\n"],
// &[::core::fmt::ArgumentV1::new_display(&"i32")],
// ));
// };
// }
Macro debugging techniques:
// 1. Use compile_error! for debug messages
macro_rules! debug_macro {
($x:expr) => {
compile_error!(concat!("Matched expr: ", stringify!($x)));
};
}
// 2. Use trace_macros! (nightly only)
#![feature(trace_macros)]
trace_macros!(true);
my_macro!(some input);
trace_macros!(false);
// 3. Use log_syntax! (nightly only)
#![feature(log_syntax)]
macro_rules! logging_macro {
($($tt:tt)*) => {
log_syntax!($($tt)*);
};
}
Macros need special export handling:
// In your library crate (lib.rs)
#[macro_export]
macro_rules! my_macro {
() => { println!("Hello from macro!"); };
}
// Optional: Re-export at crate root for better ergonomics
pub use my_macro;
// In user code
use my_crate::my_macro;
my_macro!();
// Or with #[macro_use]
#[macro_use]
extern crate my_crate;
my_macro!(); // Available without import
Module-local macros:
mod my_module {
// Not exported, only visible in this module
macro_rules! local_macro {
() => { println!("Local only"); };
}
pub fn use_it() {
local_macro!(); // OK
}
}
// my_module::local_macro!(); // Error: not visible
// Macros must be defined before use in the same file
macro_rules! early {
() => { println!("Early"); };
}
early!(); // OK
// late!(); // Error: not yet defined
macro_rules! late {
() => { println!("Late"); };
}
late!(); // OK
2. Using $crate for hygiene:
#[macro_export]
macro_rules! use_crate_item {
() => {
// $crate always refers to the crate where the macro is defined
$crate::internal::helper()
};
}
pub mod internal {
pub fn helper() {
println!("Helper from defining crate");
}
}
3. Capturing macro input for inspection:
macro_rules! inspect {
($($tt:tt)*) => {{
println!("Input tokens: {}", stringify!($($tt)*));
// Process the tokens...
}};
}
inspect!(let x = 42; println!("{}", x););
// Prints: Input tokens: let x = 42; println!("{}", x);
// Good: DSL for routing
route! {
GET "/users" => list_users,
POST "/users" => create_user,
GET "/users/:id" => get_user,
}
// Good: Zero-cost wrapper
macro_rules! measure_time {
($name:expr, $block:block) => {{
let start = std::time::Instant::now();
let result = $block;
println!("{}: {:?}", $name, start.elapsed());
result
}};
}
// Bad: Use a function instead
macro_rules! add {
($a:expr, $b:expr) => { $a + $b };
}
// Good: Just use a function
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
// Bad: Too complex for declarative macro
macro_rules! complex_trait_impl {
// 200 lines of complex pattern matching...
}
// Good: Use a procedural macro instead
#[derive(MyComplexTrait)]
struct MyStruct;
// BAD: Unreadable, unmaintainable
macro_rules! monster {
(@internal @state $s:ident @@ $($acc:tt)*) => { ... };
(@process [$($a:tt)*] => {$($b:tt)*} : $($rest:tt)*) => { ... };
// 50 more internal rules...
}
// GOOD: Break into smaller macros or use proc macro
macro_rules! simple_step_1 {
($input:tt) => { ... };
}
macro_rules! simple_step_2 {
($input:tt) => { ... };
}
// BAD: Cryptic error when pattern doesn't match
macro_rules! bad_errors {
($x:expr, $y:expr) => { $x + $y };
}
// Error: no rules expected 3 parameters
// bad_errors!(1, 2, 3);
// GOOD: Provide helpful error for common mistakes
macro_rules! good_errors {
($x:expr, $y:expr) => { $x + $y };
($($args:expr),*) => {
compile_error!(
"Expected exactly 2 arguments. \
Usage: good_errors!(expr1, expr2)"
)
};
}
// BAD: Fails with trailing comma
macro_rules! bad_vec {
($($x:expr),*) => {
vec![$($x),*]
};
}
// bad_vec![1, 2, 3,]; // Error!
// GOOD: Handle trailing comma
macro_rules! good_vec {
($($x:expr),* $(,)?) => {
vec![$($x),*]
};
}
good_vec![1, 2, 3,]; // OK!
// BAD: Macro not accessible outside crate
macro_rules! internal_only {
() => { println!("I'm trapped!"); };
}
// GOOD: Export for public use
#[macro_export]
macro_rules! publicly_available {
() => { println!("I'm free!"); };
}
// BAD: Relies on implicit variable names
macro_rules! assume_x_exists {
($val:expr) => {
x = $val; // Assumes 'x' exists in caller's scope!
};
}
// GOOD: Explicit variable handling
macro_rules! safe_assignment {
($var:ident = $val:expr) => {
$var = $val; // Caller provides the variable name
};
}
let mut x = 0;
safe_assignment!(x = 42); // Explicit and clear
// BAD: Using tt for everything
macro_rules! vague {
($($tt:tt)*) => {
// What is $tt? Who knows!
};
}
// GOOD: Use specific fragment specifiers
macro_rules! precise {
($name:ident: $ty:ty = $value:expr) => {
let $name: $ty = $value;
};
}
// Compile time increases with:
// 1. Number of macro invocations
// 2. Complexity of pattern matching
// 3. Size of generated code
// Expensive: Many invocations generating large code
macro_rules! generate_big_match {
($($variant:ident => $value:expr),*) => {
match input {
$(
Variant::$variant => {
// 50 lines of code per variant
}
)*
}
};
}
// Called 100 times = 100 * 50 * N lines of code to compile
// Better: Generate once, reuse
static DISPATCH_TABLE: &[fn()] = &[
// Generated once
];
// Macros can increase binary size if not careful
// BAD: Generates duplicate code at each call site
macro_rules! inline_everything {
($x:expr) => {{
// 100 lines of code
// Gets duplicated at EVERY call site
}};
}
// Called 50 times = 50 * 100 lines in binary
// GOOD: Extract common logic to function
fn shared_logic() {
// 100 lines of code once
}
macro_rules! inline_thin_wrapper {
($x:expr) => {
shared_logic() // Single function call
};
}
// Declarative macros have ZERO runtime cost
// They expand at compile time and disappear
macro_rules! compute_at_compile_time {
($x:expr) => {
$x * 2 + 10
};
}
// This:
let value = compute_at_compile_time!(21);
// Compiles to exactly the same as:
let value = 21 * 2 + 10;
// No function call, no indirection, no overhead
// Macro: Zero runtime cost, compile-time cost
macro_rules! macro_version {
($x:expr) => { $x * 2 };
}
// Inline function: Zero runtime cost with optimization
#[inline(always)]
fn inline_version(x: i32) -> i32 {
x * 2
}
// Regular function: Tiny call overhead (usually optimized away)
fn function_version(x: i32) -> i32 {
x * 2
}
// Benchmark (release mode, -O3):
// macro_version: ~0.1ns (compile-time substitution)
// inline_version: ~0.1ns (inlined by compiler)
// function_version: ~0.1ns (inlined by LLVM)
//
// Winner: All tied! Modern compilers optimize well.
// Use macros when you need syntactic flexibility, not performance.
// Macros don't use heap or stack at runtime
// They're purely compile-time transformations
macro_rules! no_runtime_cost {
() => {{
let x = 42; // Stack allocated like any other code
x
}};
}
// Memory usage: Same as hand-written code
let a = no_runtime_cost!();
let b = { let x = 42; x }; // Identical assembly
Create a hashmap! macro that supports multiple syntaxes:
// Your implementation here:
macro_rules! hashmap {
// TODO: Implement
}
// Should work with these test cases:
#[cfg(test)]
mod tests {
use std::collections::HashMap;
#[test]
fn test_empty() {
let map: HashMap<i32, i32> = hashmap![];
assert!(map.is_empty());
}
#[test]
fn test_single_pair() {
let map = hashmap!["key" => 42];
assert_eq!(map.get("key"), Some(&42));
}
#[test]
fn test_multiple_pairs() {
let map = hashmap![
"one" => 1,
"two" => 2,
"three" => 3,
];
assert_eq!(map.len(), 3);
assert_eq!(map.get("two"), Some(&2));
}
#[test]
fn test_type_inference() {
let map = hashmap![1 => "one", 2 => "two"];
let _: &HashMap<i32, &str> = ↦
}
}
Hints:
()($($key:expr => $value:expr),* $(,)?)std::collections::HashMap::new() and insert()Build a try_chain! macro that makes sequential Result operations more readable:
// Your implementation here:
macro_rules! try_chain {
// TODO: Implement
}
// Should work like this:
fn parse_and_process(input: &str) -> Result<i32, String> {
try_chain! {
parse_number(input) => num,
validate_range(num, 0, 100) => validated,
process_value(validated) => result,
result
}
}
// Instead of:
fn parse_and_process_manual(input: &str) -> Result<i32, String> {
let num = parse_number(input)?;
let validated = validate_range(num, 0, 100)?;
let result = process_value(validated)?;
Ok(result)
}
// Test helpers:
fn parse_number(s: &str) -> Result<i32, String> {
s.parse().map_err(|_| "Parse error".to_string())
}
fn validate_range(n: i32, min: i32, max: i32) -> Result<i32, String> {
if n >= min && n <= max {
Ok(n)
} else {
Err("Out of range".to_string())
}
}
fn process_value(n: i32) -> Result<i32, String> {
Ok(n * 2)
}
#[cfg(test)]
mod tests {
#[test]
fn test_successful_chain() {
let result = parse_and_process("42");
assert_eq!(result, Ok(84));
}
#[test]
fn test_failed_parse() {
let result = parse_and_process("not a number");
assert!(result.is_err());
}
#[test]
fn test_failed_validation() {
let result = parse_and_process("150");
assert!(result.is_err());
}
}
Hints:
($func:expr => $var:ident, $($rest:tt)*)($final:expr)let $var = $func?; for each stepImplement a mini SQL DSL that validates queries at compile time:
// Your implementation here:
macro_rules! sql {
// TODO: Implement SELECT
// TODO: Implement INSERT
// TODO: Implement UPDATE
// TODO: Implement DELETE
// TODO: Add WHERE clause support
// TODO: Add JOIN support (bonus!)
}
// Should support:
// 1. SELECT queries
let query = sql! {
SELECT id, name, email
FROM users
WHERE status = "active"
};
// 2. INSERT queries
let query = sql! {
INSERT INTO users (name, email)
VALUES ("Alice", "alice@example.com")
};
// 3. UPDATE queries
let query = sql! {
UPDATE users
SET status = "inactive"
WHERE id = 42
};
// 4. DELETE queries
let query = sql! {
DELETE FROM users
WHERE last_login < "2020-01-01"
};
// 5. JOIN queries (bonus!)
let query = sql! {
SELECT users.name, orders.total
FROM users
JOIN orders ON users.id = orders.user_id
WHERE orders.total > 100
};
// Query structure to return
struct SqlQuery {
query: String,
params: Vec<String>,
}
impl SqlQuery {
fn bind<T: ToString>(mut self, value: T) -> Self {
self.params.push(value.to_string());
self
}
fn execute(&self) -> QueryResult {
// Simulate execution
println!("Executing: {}", self.query);
println!("Params: {:?}", self.params);
QueryResult { rows_affected: 1 }
}
}
struct QueryResult {
rows_affected: usize,
}
#[cfg(test)]
mod tests {
#[test]
fn test_select() {
let query = sql! {
SELECT id, name FROM users WHERE id = 1
};
assert!(query.query.contains("SELECT"));
assert!(query.query.contains("FROM users"));
}
#[test]
fn test_insert() {
let query = sql! {
INSERT INTO users (name) VALUES ("Bob")
};
assert!(query.query.contains("INSERT INTO"));
}
#[test]
fn test_parameterized() {
let user_id = 42;
let query = sql! {
SELECT * FROM users WHERE id = ?
}.bind(user_id);
assert_eq!(query.params, vec!["42"]);
}
}
Hints:
(@select ...), (@insert ...), etc.SELECT, FROM, WHERE, INSERT, etc.$($col:ident),+format!() macro// vec! - Collection initialization
let v = vec![1, 2, 3, 4, 5];
// println! / format! - Formatted output
println!("Hello, {}!", "world");
// matches! - Pattern matching test
if matches!(value, Some(42) | Some(43)) {
// ...
}
// dbg! - Debug printing
let x = dbg!(expensive_computation());
// assert! family - Testing
assert_eq!(left, right);
assert_ne!(a, b);
// concat! - Compile-time string concatenation
const MSG: &str = concat!("Version ", env!("CARGO_PKG_VERSION"));
// include_str! / include_bytes! - Embed files at compile time
const TEMPLATE: &str = include_str!("template.html");
let value = json!({
"name": "Alice",
"age": 30,
"emails": ["alice@example.com"],
"active": true
});
anyhow - Error handling:
use anyhow::{anyhow, bail, Context, Result};
fn process() -> Result<()> {
if condition {
bail!("Something went wrong");
}
let value = compute().context("Failed to compute")?;
Ok(())
}
lazy_static! - Lazy initialization:
lazy_static! {
static ref REGEX: Regex = Regex::new(r"^\d+__CODE_BLOCK_2__quot;).unwrap();
static ref CONFIG: Config = load_config();
}
tokio::select! - Async multiplexing (partially declarative):
tokio::select! {
result = async_operation_1() => {
println!("Op1 completed: {:?}", result);
}
result = async_operation_2() => {
println!("Op2 completed: {:?}", result);
}
}
proptest! - Property testing:
proptest! {
#[test]
fn test_reversing_twice(vec: Vec<i32>) {
let mut v = vec.clone();
v.reverse();
v.reverse();
assert_eq!(v, vec);
}
}
config! {
database {
host: "localhost",
port: 5432,
pool_size: 10,
}
cache {
redis: "redis://localhost",
ttl: 3600,
}
features {
enable: ["feature_a", "feature_b"],
disable: ["legacy_mode"],
}
}
Routing Tables:
routes! {
GET "/api/users" => list_users,
POST "/api/users" => create_user,
GET "/api/users/:id" => get_user,
PUT "/api/users/:id" => update_user,
DELETE "/api/users/:id" => delete_user,
}
Protocol State Machines:
protocol_fsm! {
states {
Idle,
Connecting,
Connected,
Disconnecting,
}
transitions {
Idle -> Connecting on Connect,
Connecting -> Connected on ConnectionEstablished,
Connected -> Disconnecting on Disconnect,
Disconnecting -> Idle on DisconnectionComplete,
}
}
---
Next Steps: After mastering declarative macros, explore:Run this code in the official Rust Playground