Declarative and Procedural Macros in Depth

Macro Repetition

Macro repetition allows a macro_rules! pattern to match and expand variable-length sequences of tokens using the syntax $( ... ) sep rep_op, where sep is an optional separator token (such as , or ;) and rep_op is one of three repetition kleene operators: *, +, or ?.

Repetition Operators

Operator Matcher Syntax Expansion Syntax Meaning
* (Zero or more) $($x:expr),* $($x),* Matches 0, 1, or more occurrences separated by ,
+ (One or more) $($x:expr),+ $($x),+ Matches 1 or more occurrences separated by ,
? (Zero or one) $(,)? or $($vis:vis)? $($x)? Optional element (no separator allowed before ?!)

Supporting Optional Trailing Commas ($(,)?)

Appending $(,)? after a comma-separated repetition $($elem:expr),+ $(,)? allows callers to write either my_macro!(1, 2, 3) (no trailing comma) or multi-line my_macro!(1, 2, 3,) (with a trailing comma) inside a single rule!

Parallel Zipping and Nested Repetitions

When two different metavariables $keys and $vals are repeated at the same nesting depth in the matcher, expanding $($keys => $vals),* in the transcriber zips them together in lockstep (requiring both sequences to have the exact same length):

use std::collections::HashMap;

macro_rules! zip_map {
    // Matches two parallel bracketed lists `[k1, k2, ...] => [v1, v2, ...]`
    ( [ $($key:expr),* $(,)? ] => [ $($val:expr),* $(,)? ] ) => {{
        let mut map = HashMap::new();
        $(
            map.insert($key, $val);
        )*
        map
    }};
}

fn main() {
    let scores = zip_map!(
        ["alice", "bob", "carol",] => [95, 88, 92,]
    );
    println!("alice = {}, carol = {}", scores["alice"], scores["carol"]);
}
alice = 95, carol = 92

Metavariable Expressions in Expansions (${count(...)}, ${index()})

When generating array types [T; N] or indexed tuple fields from a repetition, counting how many elements matched $($elem:expr),* traditionally required a recursive helper macro or a slice-length trick <[_]>::len(&[ $( replace_expr!($elem, ()) ),* ]):

macro_rules! replace_with_unit {
    ($_t:tt) => {
        ()
    };
}

macro_rules! fixed_array_with_len {
    ( $( $item:expr ),* $(,)? ) => {{
        // Computes the exact compile-time count of `$item` repetitions:
        const LEN: usize = <[()]>::len(&[ $( replace_with_unit!($item) ),* ]);
        let arr: [_; LEN] = [ $( $item ),* ];
        (LEN, arr)
    }};
}

fn main() {
    let (len, arr) = fixed_array_with_len![10, 20, 30, 40];
    println!("compile-time len = {}, array = {:?}", len, arr);
}
compile-time len = 4, array = [10, 20, 30, 40]