Home/Macro Mastery/Declarative Macros

Declarative Macros

macro_rules! patterns and repetition

intermediate
macro_rulesdeclarative
🎮 Interactive Playground

What are Declarative Macros?

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:
  • Pattern matching: Match different input forms with different rules
  • Compile-time expansion: Zero runtime overhead
  • Hygiene: Variables don't accidentally capture scope
  • Fragment specifiers: Type-safe matching of expressions, types, identifiers, etc.
  • Repetition: Handle variable-length inputs with $(...)* patterns

Real-World Examples

Example 1: HashMap Literal Macro (Systems Programming)

Creating 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 = 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:
  • Eliminates repetitive insert() calls
  • Enforces consistent initialization patterns across teams
  • Type inference means less type annotation noise
  • Compile-time expansion means zero runtime cost
  • Used in configuration, routing tables, caching layers

Example 2: SQL Query Builder DSL (Web/Backend)

A 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:
  • Prevents SQL injection through parameterized queries
  • Compile-time syntax checking catches typos
  • Reduces runtime query construction overhead
  • Enforces consistent query patterns across codebase
  • Used in ORMs, database access layers, migration tools

Example 3: Enhanced Assert Macros (Testing/Debugging)

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:
  • Better error messages save debugging time
  • Context capture eliminates "why did this fail?" questions
  • File/line info pinpoints failures instantly
  • Sorted comparison eliminates order-dependent test flakiness
  • Debug-only assertions maintain release performance

Example 4: Structured Logging Macro (Observability)

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:
  • Structured logging enables log aggregation and searching
  • Compile-time level filtering eliminates runtime checks
  • Zero cost when disabled via feature flags
  • Consistent log format across entire codebase
  • Automatic context capture (file, line, function)

Example 5: Bitflags and Enum Generation (Low-level)

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:
  • Type-safe bit manipulation prevents masking errors
  • Generated trait implementations ensure consistent behavior
  • Debug output makes debugging bit operations trivial
  • Zero-cost abstractions over raw bit operations
  • Used in: network protocols, file systems, permissions, hardware interfaces

Deep Dive Explanation

Macro Syntax and Grammar

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
  • {} - Braces
macro_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

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 Patterns

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

Macro Hygiene

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);

Internal Rules Pattern

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,
    }
}

Recursive Macros

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

Debugging with cargo expand

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)*);
    };
}

Exporting and Using Macros

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

Advanced Techniques

1. Macro scope and ordering:
// 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);

When to Use / When NOT to Use

Use Declarative Macros When:

  1. Creating DSLs: Custom syntax for specific domains (SQL, routing, config)
  2. Reducing boilerplate: Repetitive patterns that can't be abstracted with functions
  3. Compile-time computation: Generating code based on static information
  4. Zero-cost abstractions: Need syntactic sugar without runtime overhead
  5. Multiple input patterns: Different calling conventions map to same functionality
  6. Type-level programming: Working with types at compile time
  7. Simple code generation: Struct derives, builder patterns, enum conversions
// 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
    }};
}

Don't Use Declarative Macros When:

  1. Functions work: Regular functions with generics can solve it
  2. Complex AST manipulation: Need to analyze/transform complex syntax trees
  3. Error messages matter: Need precise, context-aware error messages
  4. IDE support critical: Want full autocomplete and navigation
  5. Dynamic behavior: Runtime logic is better in functions
  6. Team unfamiliarity: Maintenance burden outweighs benefits
  7. Proc macros better: Custom derives, attributes, or function-like macros with complex parsing
// 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;

⚠️ Anti-patterns

⚠️ Anti-pattern 1: Over-Complex Macros

// 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) => { ... };
}

⚠️ Anti-pattern 2: Poor Error Messages

// 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)"
        )
    };
}

⚠️ Anti-pattern 3: Not Handling Edge Cases

// 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!

⚠️ Anti-pattern 4: Forgetting #[macro_export]

// 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!"); };
}

⚠️ Anti-pattern 5: Hygiene Violations

// 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

⚠️ Anti-pattern 6: Token Tree Abuse

// 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;
    };
}

Performance Characteristics

Compile-Time Impact

// 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
];

Binary Size

// 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
    };
}

Runtime Performance: Zero-Cost Abstraction

// 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

Performance vs. Alternatives

// 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.

Memory Impact

// 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

Exercises

Beginner: HashMap Literal Macro

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> = &map;
    }
}
Hints:
  • Handle empty case: ()
  • Handle main case: ($($key:expr => $value:expr),* $(,)?)
  • Use std::collections::HashMap::new() and insert()
  • Return a HashMap, not a reference

Intermediate: Result Chaining Macro

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:
  • Pattern: ($func:expr => $var:ident, $($rest:tt)*)
  • Use recursion to process each step
  • Base case: ($final:expr)
  • Generate: let $var = $func?; for each step

Advanced: SQL DSL with Compile-Time Validation

Implement 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:
  • Use internal rules: (@select ...), (@insert ...), etc.
  • Pattern match on keywords: SELECT, FROM, WHERE, INSERT, etc.
  • Use repetitions for column lists: $($col:ident),+
  • Build query string with format!() macro
  • For WHERE clause, use recursive pattern matching
  • For JOINs, nest the parsing with more internal rules
Bonus challenges:
  • Add ORDER BY and LIMIT support
  • Validate column names against a table schema at compile time
  • Generate prepared statement placeholders automatically
  • Support subqueries with nested sql! invocations

Real-World Usage

Standard Library Examples

// 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");

Popular Crate Examples

serde_json::json! - JSON literal syntax:
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);
    }
}

Enterprise Usage Patterns

Configuration DSLs:
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,
    }
}

Further Reading

Official Documentation

Tools

Advanced Topics

RFCs and Discussions

Community Resources

---

Next Steps: After mastering declarative macros, explore:
  • Procedural Macros: Custom derives, attributes, and function-like macros
  • Macro Hygiene: Deep dive into scope and span resolution
  • Compiler Plugins: Extending the Rust compiler (unstable)
  • DSL Design: Building domain-specific languages with macros

🎮 Try it Yourself

🎮

Declarative Macros - Playground

Run this code in the official Rust Playground