BBuzzard

macro_rules! defines a macro by pattern matching on tokens. Each rule is (matcher) => { expansion }. Matchers capture fragments: $x:expr for an expression, $t:ty a type, $i:ident a name, $l:literal a literal, $p:pat a pattern, $b:block a block, and $tt:tt any single token tree. $( ... ),* repeats zero or more times with a comma separator; + means one or more and ? means optional.

Macros expand before type checking, which is why println! can take any number of arguments of any type. They are hygienic: a variable created inside a macro does not clash with one of the same name at the call site. Rules are tried top to bottom, and a macro may call itself recursively.

Prefer a function when a function will do. Reach for a macro for syntax a function cannot express: variadic arguments, generating items, or taking code as input.

Simple expression macros, and why expr captures matter

macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

macro_rules! square {
    ($x:expr) => {
        $x * $x
    };
}

macro_rules! square_safe {
    ($x:expr) => {{
        let v = $x;
        v * v
    }};
}

fn demo_basics() {
    println!("-- basics --");

    println!("{} {}", add!(2, 3), add!(10, 20));

    // $x:expr is captured as one expression, so square!(1 + 2) is (1 + 2) * (1 + 2).
    println!("square!(1 + 2) = {}", square!(1 + 2));

    let mut n = 0;
    let mut next = || {
        n += 1;
        n
    };

    // square! evaluates its argument twice; square_safe! once.
    println!(
        "square!(next()) = {} square_safe!(next()) = {}",
        square!(next()),
        square_safe!(next())
    );
}

Repetition and recursion

macro_rules! vec_of {
    ($($x:expr),* $(,)?) => {{
        #[allow(unused_mut)] // vec_of![] never pushes
        let mut v = Vec::new();
        $(v.push($x);)*
        v
    }};
}

macro_rules! hashmap {
    ($($k:expr => $v:expr),* $(,)?) => {{
        let mut m = HashMap::new();
        $(m.insert($k, $v);)*
        m
    }};
}

macro_rules! max_of {
    ($x:expr) => { $x };
    ($x:expr, $($rest:expr),+) => {{
        let a = $x;
        let b = max_of!($($rest),+);
        if a > b { a } else { b }
    }};
}

fn demo_repetition() {
    println!("-- repetition --");

    println!("{:?} {:?}", vec_of![1, 2, 3], vec_of![4, 5,]);
    let empty: Vec<i32> = vec_of![];
    println!("empty {empty:?}");
    let m = hashmap! { "a" => 1, "b" => 2 };
    let mut pairs: Vec<_> = m.into_iter().collect();
    pairs.sort();
    println!("{pairs:?}");
    println!("max_of {}", max_of!(3, 9, 4, 1));
}

Several rules, matched on literal tokens

macro_rules! calc {
    (add $a:literal $b:literal) => {
        $a + $b
    };
    (mul $a:literal $b:literal) => {
        $a * $b
    };
    (neg $a:literal) => {
        -$a
    };
}

fn demo_rules() {
    println!("-- multiple rules --");

    println!("{} {} {}", calc!(add 2 3), calc!(mul 4 5), calc!(neg 7));

    // calc!(div 1 2); // error: no rules expected the token `div`
}

Generating items: structs, methods, and trait impls

macro_rules! getters {
    ($name:ident { $($field:ident : $ty:ty),* $(,)? }) => {
        #[derive(Debug, Default)]
        struct $name {
            $($field: $ty),*
        }

        impl $name {
            $(fn $field(&self) -> &$ty { &self.$field })*
        }
    };
}

getters!(Profile { name: String, age: u32 });

macro_rules! impl_describe {
    ($($t:ty),*) => {
        $(impl Describe for $t {
            fn describe(&self) -> String {
                format!("{} = {:?}", stringify!($t), self)
            }
        })*
    };
}

trait Describe {
    fn describe(&self) -> String;
}

impl_describe!(i32, bool, &str);

fn demo_items() {
    println!("-- generating items --");

    let p = Profile { name: "ada".into(), age: 36 };
    println!("{} {} {:?}", p.name(), p.age(), Profile::default());
    println!("{} | {} | {}", 5.describe(), true.describe(), "hi".describe());
}

Taking a block, and hygiene

macro_rules! time_it {
    ($label:literal, $body:block) => {{
        let start = std::time::Instant::now();
        let out = $body;
        println!("  {} took under 1s: {}", $label, start.elapsed().as_secs() < 1);
        out
    }};
}

macro_rules! swap_hygiene {
    ($a:ident, $b:ident) => {
        let tmp = $a;
        $a = $b;
        $b = tmp;
    };
}

fn demo_blocks_hygiene() {
    println!("-- blocks and hygiene --");

    let total = time_it!("summing", { (1..=100).sum::<i32>() });

    println!("total {total}");
    let tmp = "outer tmp";
    let mut x = 1;
    let mut y = 2;
    swap_hygiene!(x, y);

    // The macro's `tmp` did not overwrite ours.
    println!("x {x} y {y} tmp {tmp:?}");
}

Useful built-in macros

fn demo_builtins() {
    println!("-- built-in macros --");

    println!("stringify!: {}", stringify!(a + b * 2));
    println!("concat!: {}", concat!("v", 1, ".", 2, '-', true));
    println!("line {} column {} file {}", line!(), column!(), file!());
    println!("module_path {}", module_path!());
    println!("matches! {}", matches!(Some(3), Some(n) if n > 2));

    let v = vec![1; 3];
    println!("vec! {v:?} format! {}", format!("{:>4}", 7));

    // compile_error!("stops the build with this message");
}
$ cargo run --bin 55_macros
-- basics --
5 30
square!(1 + 2) = 9
square!(next()) = 2 square_safe!(next()) = 9
-- repetition --
[1, 2, 3] [4, 5]
empty []
[("a", 1), ("b", 2)]
max_of 9
-- multiple rules --
5 20 -7
-- generating items --
ada 36 Profile { name: "", age: 0 }
i32 = 5 | bool = true | &str = "hi"
-- blocks and hygiene --
  summing took under 1s: true
total 5050
x 2 y 1 tmp "outer tmp"
-- built-in macros --
stringify!: a + b * 2
concat!: v1.2-true
line 203 column 52 file 55_macros/main.rs
module_path 55_macros
matches! true
vec! [1, 1, 1] format!    7

Full program

55_macros/main.rs · 220 lines
View on GitHub
// 55. Declarative macros with macro_rules!
//
// macro_rules! defines a macro by pattern matching on tokens. Each rule is
// (matcher) => { expansion }. Matchers capture fragments: $x:expr for an expression,
// $t:ty a type, $i:ident a name, $l:literal a literal, $p:pat a pattern, $b:block a
// block, and $tt:tt any single token tree. $( ... ),* repeats zero or more times with a
// comma separator; + means one or more and ? means optional.
//
// Macros expand before type checking, which is why println! can take any number of
// arguments of any type. They are hygienic: a variable created inside a macro does not
// clash with one of the same name at the call site. Rules are tried top to bottom, and
// a macro may call itself recursively.
//
// Prefer a function when a function will do. Reach for a macro for syntax a function
// cannot express: variadic arguments, generating items, or taking code as input.
//
// Run: cargo run --bin 55_macros

use std::collections::HashMap;

macro_rules! add {
    ($a:expr, $b:expr) => {
        $a + $b
    };
}

macro_rules! square {
    ($x:expr) => {
        $x * $x
    };
}

macro_rules! square_safe {
    ($x:expr) => {{
        let v = $x;
        v * v
    }};
}

// Example 1: simple expression macros, and why expr captures matter.
fn demo_basics() {
    println!("-- basics --");

    println!("{} {}", add!(2, 3), add!(10, 20));

    // $x:expr is captured as one expression, so square!(1 + 2) is (1 + 2) * (1 + 2).
    println!("square!(1 + 2) = {}", square!(1 + 2));

    let mut n = 0;
    let mut next = || {
        n += 1;
        n
    };

    // square! evaluates its argument twice; square_safe! once.
    println!(
        "square!(next()) = {} square_safe!(next()) = {}",
        square!(next()),
        square_safe!(next())
    );
}

macro_rules! vec_of {
    ($($x:expr),* $(,)?) => {{
        #[allow(unused_mut)] // vec_of![] never pushes
        let mut v = Vec::new();
        $(v.push($x);)*
        v
    }};
}

macro_rules! hashmap {
    ($($k:expr => $v:expr),* $(,)?) => {{
        let mut m = HashMap::new();
        $(m.insert($k, $v);)*
        m
    }};
}

macro_rules! max_of {
    ($x:expr) => { $x };
    ($x:expr, $($rest:expr),+) => {{
        let a = $x;
        let b = max_of!($($rest),+);
        if a > b { a } else { b }
    }};
}

// Example 2: repetition and recursion.
fn demo_repetition() {
    println!("-- repetition --");

    println!("{:?} {:?}", vec_of![1, 2, 3], vec_of![4, 5,]);
    let empty: Vec<i32> = vec_of![];
    println!("empty {empty:?}");
    let m = hashmap! { "a" => 1, "b" => 2 };
    let mut pairs: Vec<_> = m.into_iter().collect();
    pairs.sort();
    println!("{pairs:?}");
    println!("max_of {}", max_of!(3, 9, 4, 1));
}

macro_rules! calc {
    (add $a:literal $b:literal) => {
        $a + $b
    };
    (mul $a:literal $b:literal) => {
        $a * $b
    };
    (neg $a:literal) => {
        -$a
    };
}

// Example 3: several rules, matched on literal tokens.
fn demo_rules() {
    println!("-- multiple rules --");

    println!("{} {} {}", calc!(add 2 3), calc!(mul 4 5), calc!(neg 7));

    // calc!(div 1 2); // error: no rules expected the token `div`
}

macro_rules! getters {
    ($name:ident { $($field:ident : $ty:ty),* $(,)? }) => {
        #[derive(Debug, Default)]
        struct $name {
            $($field: $ty),*
        }

        impl $name {
            $(fn $field(&self) -> &$ty { &self.$field })*
        }
    };
}

getters!(Profile { name: String, age: u32 });

macro_rules! impl_describe {
    ($($t:ty),*) => {
        $(impl Describe for $t {
            fn describe(&self) -> String {
                format!("{} = {:?}", stringify!($t), self)
            }
        })*
    };
}

trait Describe {
    fn describe(&self) -> String;
}

impl_describe!(i32, bool, &str);

// Example 4: generating items: structs, methods, and trait impls.
fn demo_items() {
    println!("-- generating items --");

    let p = Profile { name: "ada".into(), age: 36 };
    println!("{} {} {:?}", p.name(), p.age(), Profile::default());
    println!("{} | {} | {}", 5.describe(), true.describe(), "hi".describe());
}

macro_rules! time_it {
    ($label:literal, $body:block) => {{
        let start = std::time::Instant::now();
        let out = $body;
        println!("  {} took under 1s: {}", $label, start.elapsed().as_secs() < 1);
        out
    }};
}

macro_rules! swap_hygiene {
    ($a:ident, $b:ident) => {
        let tmp = $a;
        $a = $b;
        $b = tmp;
    };
}

// Example 5: taking a block, and hygiene.
fn demo_blocks_hygiene() {
    println!("-- blocks and hygiene --");

    let total = time_it!("summing", { (1..=100).sum::<i32>() });

    println!("total {total}");
    let tmp = "outer tmp";
    let mut x = 1;
    let mut y = 2;
    swap_hygiene!(x, y);

    // The macro's `tmp` did not overwrite ours.
    println!("x {x} y {y} tmp {tmp:?}");
}

// Example 6: useful built-in macros.
fn demo_builtins() {
    println!("-- built-in macros --");

    println!("stringify!: {}", stringify!(a + b * 2));
    println!("concat!: {}", concat!("v", 1, ".", 2, '-', true));
    println!("line {} column {} file {}", line!(), column!(), file!());
    println!("module_path {}", module_path!());
    println!("matches! {}", matches!(Some(3), Some(n) if n > 2));

    let v = vec![1; 3];
    println!("vec! {v:?} format! {}", format!("{:>4}", 7));

    // compile_error!("stops the build with this message");
}

fn main() {
    demo_basics();
    demo_repetition();
    demo_rules();
    demo_items();
    demo_blocks_hygiene();
    demo_builtins();
}