Declarative Macros

Overview

Declarative macros (also called “macros by example” or macro_rules! macros) let you write code that writes code. They match patterns in the input and expand to Rust code at compile time.

flowchart LR
    A["vec![1, 2, 3]"] -->|"Macro Expansion"| B["Compiler"]
    B --> C["{<br/>let mut v = Vec::new();<br/>v.push(1);<br/>v.push(2);<br/>v.push(3);<br/>v<br/>}"]

Key insight: Macros operate on tokens (syntax), not values. They’re expanded before type checking, allowing patterns impossible with functions.

When to Use Macros

Use Case Macro Function
Variable number of arguments Yes No (use slice)
Generate repetitive code Yes No
DSL (domain-specific language) Yes No
Compile-time computation Yes const fn
Type-safe operations No Yes (preferred)
flowchart TD
    A{Need variable args?} -->|Yes| B["Macro"]
    A -->|No| C{Generate code patterns?}
    C -->|Yes| B
    C -->|No| D{Need custom syntax?}
    D -->|Yes| B
    D -->|No| E["Use function instead"]

Basic Macro Syntax

macro_rules! macro_name {
    // Pattern => Expansion
    (pattern) => {
        // Generated code
    };
}

Simple Example

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

fn main() {
    say_hello!();  // Expands to: println!("Hello!");
}

Fragment Specifiers

Macros capture different kinds of syntax using fragment specifiers:

Specifier Matches Example
$x:expr Expression 1 + 2, foo()
$x:ident Identifier foo, MyStruct
$x:ty Type i32, Vec<String>
$x:pat Pattern Some(x), _
$x:stmt Statement let x = 1;
$x:block Block { ... }
$x:item Item fn foo() {}
$x:path Path std::io::Read
$x:tt Token tree Any single token
$x:literal Literal 42, "hello"

Using Fragment Specifiers

macro_rules! create_function {
    ($func_name:ident) => {
        fn $func_name() {
            println!("Called {:?}()", stringify!($func_name));
        }
    };
}

create_function!(foo);  // Creates fn foo()
create_function!(bar);  // Creates fn bar()

fn main() {
    foo();  // Prints: Called "foo"()
    bar();  // Prints: Called "bar"()
}

Multiple Parameters

macro_rules! make_pair {
    ($t:ty, $first:expr, $second:expr) => {
        {
            let pair: ($t, $t) = ($first, $second);
            pair
        }
    };
}

fn main() {
    let p = make_pair!(i32, 1, 2);
    println!("{:?}", p);  // (1, 2)
}

Repetition

The real power of macros: handling variable numbers of arguments.

Syntax

$( ... )sep*   // Zero or more
$( ... )sep+   // One or more
$( ... )sep?   // Zero or one

Where sep is an optional separator (like ,).

Example: vec! Implementation

macro_rules! my_vec {
    // Empty case
    () => {
        Vec::new()
    };
    // One or more elements
    ( $( $x:expr ),+ $(,)? ) => {
        {
            let mut temp_vec = Vec::new();
            $(
                temp_vec.push($x);
            )+
            temp_vec
        }
    };
}

fn main() {
    let v = my_vec![1, 2, 3];
    println!("{:?}", v);  // [1, 2, 3]
}
flowchart TD
    A["my_vec![1, 2, 3]"] --> B["Match pattern"]
    B --> C["$x matches: 1, 2, 3"]
    C --> D["Expand repetition"]
    D --> E["temp_vec.push(1);"]
    D --> F["temp_vec.push(2);"]
    D --> G["temp_vec.push(3);"]

HashMap Literal

macro_rules! hashmap {
    ( $( $key:expr => $value:expr ),* $(,)? ) => {
        {
            let mut map = std::collections::HashMap::new();
            $(
                map.insert($key, $value);
            )*
            map
        }
    };
}

fn main() {
    let scores = hashmap! {
        "Alice" => 100,
        "Bob" => 85,
        "Carol" => 92,
    };
    println!("{:?}", scores);
}

Multiple Match Arms

Macros can have multiple patterns, tried in order:

macro_rules! calculate {
    // Single value
    ($x:expr) => {
        $x
    };
    // Addition
    ($x:expr, + , $y:expr) => {
        $x + $y
    };
    // Subtraction
    ($x:expr, - , $y:expr) => {
        $x - $y
    };
}

fn main() {
    println!("{}", calculate!(5));           // 5
    println!("{}", calculate!(5, +, 3));     // 8
    println!("{}", calculate!(10, -, 4));    // 6
}

Recursive Macros

Macros can call themselves:

macro_rules! count {
    () => { 0usize };
    ($head:tt $($tail:tt)*) => { 1usize + count!($($tail)*) };
}

fn main() {
    println!("{}", count!(a b c d e));  // 5
}

Recursive vec with Capacity

macro_rules! counted_vec {
    ($($x:expr),* $(,)?) => {
        {
            const COUNT: usize = count!($($x)*);
            let mut v = Vec::with_capacity(COUNT);
            $(v.push($x);)*
            v
        }
    };
}

macro_rules! count {
    () => { 0usize };
    ($head:expr $(, $tail:expr)*) => { 1usize + count!($($tail),*) };
}

Useful Built-in Macros

Macro Purpose
stringify!($x) Convert tokens to string literal
concat!($a, $b) Concatenate literals
file!() Current file name
line!() Current line number
column!() Current column number
module_path!() Current module path
env!("VAR") Environment variable at compile time
include_str!("file") Include file as string
include_bytes!("file") Include file as bytes
macro_rules! log_location {
    ($msg:expr) => {
        println!("[{}:{}] {}", file!(), line!(), $msg);
    };
}

fn main() {
    log_location!("Starting");  // [src/main.rs:10] Starting
}

Macro Export and Visibility

Within a Crate

// In lib.rs or a module
#[macro_use]
mod macros {
    macro_rules! my_macro {
        () => { println!("Hello!"); };
    }
}

// Now available throughout the crate

Exporting from a Crate

// Modern way (Rust 2018+)
#[macro_export]
macro_rules! public_macro {
    () => { /* ... */ };
}

// Users import with:
// use your_crate::public_macro;

Common Patterns

Builder-Style DSL

macro_rules! html {
    ($tag:ident { $($inner:tt)* }) => {
        format!("<{}>{}</{}>",
            stringify!($tag),
            html!($($inner)*),
            stringify!($tag))
    };
    ($text:literal) => {
        $text.to_string()
    };
    () => {
        String::new()
    };
}

fn main() {
    let page = html!(div { "Hello, World!" });
    println!("{}", page);  // <div>Hello, World!</div>
}

Test Generation

macro_rules! test_cases {
    ($($name:ident: $input:expr => $expected:expr),* $(,)?) => {
        $(
            #[test]
            fn $name() {
                assert_eq!(process($input), $expected);
            }
        )*
    };
}

fn process(x: i32) -> i32 { x * 2 }

test_cases! {
    test_zero: 0 => 0,
    test_positive: 5 => 10,
    test_negative: -3 => -6,
}

Enum Dispatch

macro_rules! dispatch {
    ($val:expr, $enum:ident, $method:ident $(, $arg:expr)*) => {
        match $val {
            $enum::A(inner) => inner.$method($($arg),*),
            $enum::B(inner) => inner.$method($($arg),*),
            $enum::C(inner) => inner.$method($($arg),*),
        }
    };
}

Debugging Macros

trace_macros

#![feature(trace_macros)]

trace_macros!(true);
my_macro!(arg1, arg2);
trace_macros!(false);

cargo expand

# Install
cargo install cargo-expand

# Use
cargo expand  # Shows expanded code

Hygiene

Macros are hygienic—they don’t accidentally capture variables:

macro_rules! using_x {
    ($body:expr) => {
        {
            let x = 42;  // This x is in macro's scope
            $body        // This sees caller's x, not macro's
        }
    };
}

fn main() {
    let x = 10;
    let result = using_x!(x * 2);  // Uses caller's x (10)
    println!("{}", result);  // 20, not 84
}

Summary

mindmap
  root((Declarative Macros))
    Syntax
      macro_rules!
      Pattern => Expansion
    Fragments
      expr ident ty
      pat stmt block
      item path tt
    Repetition
      Zero or more *
      One or more +
      Optional ?
    Features
      Multiple arms
      Recursion
      Hygiene
    Use Cases
      Variable args
      Code generation
      DSLs
Concept Syntax Purpose
Define macro macro_rules! name { } Create macro
Fragment $x:expr Capture syntax
Repetition $($x:expr),* Variable args
Multiple arms (pat1) => {}; (pat2) => {} Pattern matching
Export #[macro_export] Make public

See Also

Next Steps

Learn about Procedural Macros for more powerful code generation.


Back to top

Rust Programming Guide is not affiliated with the Rust Foundation. Content is provided for educational purposes.

This site uses Just the Docs, a documentation theme for Jekyll.