๐Ÿฆ€ Functional Rust

417: Implementing vec!-like Macros

Difficulty: 3 Level: Advanced Build collection-literal macros using declarative macro repetition โ€” the same pattern that powers `vec!`, `hashmap!`, and `format!`.

The Problem This Solves

Constructing collections with literal syntax is natural in many languages but Rust has no built-in syntax for `{1: "a", 2: "b"}` or `{1, 2, 3}` as a `HashSet`. You need `HashMap::new()` + repeated `insert()` calls โ€” five lines for what should be one. Rust's answer is: write the macro yourself, once, and use it everywhere. `macro_rules!` gives you pattern matching on token streams. The key pattern is repetition: `$($x:expr),` matches zero or more comma-separated expressions and `$(action)` repeats a block for each captured match. This is how `vec![1, 2, 3]` works โ€” it matches `$($x:expr),*` and expands to a series of `.push($x)` calls. Understanding this pattern lets you build `hashset!`, `hashmap!`, `deque!`, or any domain-specific collection literal. The macro runs at compile time; the output is exactly what you'd write by hand.

The Intuition

`$($x:expr),` captures "zero or more comma-separated expressions" and `$(body)` repeats the body once per capture โ€” this repetition pattern is the core of all collection-building macros.

How It Works in Rust

// Replicate how vec! works
macro_rules! my_vec {
 () => { Vec::new() };
 ($($x:expr),+ $(,)?) => {{   // $(,)? = optional trailing comma
     let mut v = Vec::new();
     $(v.push($x);)+          // expand once per captured $x
     v
 }};
}

// hashset! literal
macro_rules! hashset {
 ($($x:expr),* $(,)?) => {{
     let mut s = std::collections::HashSet::new();
     $(s.insert($x);)*
     s
 }};
}

// hashmap! with key => value syntax
macro_rules! hashmap {
 ($($k:expr => $v:expr),* $(,)?) => {{
     let mut m = std::collections::HashMap::new();
     $(m.insert($k, $v);)*
     m
 }};
}

// Usage
let v = my_vec![1, 2, 3, 4];
let s = hashset!["a", "b", "c"];
let m = hashmap!["one" => 1, "two" => 2,];  // trailing comma ok
1. `macro_rules! name { (pattern) => { expansion } }` โ€” arms match like `match`. 2. `$x:expr` captures one expression. `$($x:expr),*` captures a comma-separated list. 3. `$(body)+` expands `body` once per element (requires โ‰ฅ1). `*` allows zero. 4. `$(,)?` at the end accepts an optional trailing comma โ€” good style.

What This Unlocks

Key Differences

ConceptOCamlRust
List literal`[1; 2; 3]` built-in`vec![1, 2, 3]` via macro
Custom syntaxPPX (preprocessor extensions)`macro_rules!` declarative macros
Repetition patternCamlp4/PPX sequence matching`$($x:expr),` + `$(body)`
Compile-time expansionPPX (complex setup)`macro_rules!` โ€” built into language
Map literalNo standard literalWrite `hashmap!` macro yourself
// Implementing vec!-like macros in Rust
use std::collections::{HashSet, HashMap, VecDeque};

// Clone of vec! to understand it
macro_rules! my_vec {
    // Empty
    () => { Vec::new() };
    // With elements โ€” trailing comma optional
    ($($x:expr),+ $(,)?) => {
        {
            let mut v = Vec::with_capacity(count_args!($($x),+));
            $(v.push($x);)+
            v
        }
    };
}

// Helper for capacity
macro_rules! count_args {
    () => { 0usize };
    ($head:expr $(, $tail:expr)*) => { 1 + count_args!($($tail),*) };
}

// hashset! literal
macro_rules! hashset {
    ($($x:expr),* $(,)?) => {
        {
            let mut s = HashSet::new();
            $(s.insert($x);)*
            s
        }
    };
}

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

// deque! literal
macro_rules! deque {
    ($($x:expr),* $(,)?) => {
        {
            let mut d = VecDeque::new();
            $(d.push_back($x);)*
            d
        }
    };
}

// Custom type with literal macro
struct Matrix {
    rows: usize,
    cols: usize,
    data: Vec<f64>,
}

macro_rules! matrix {
    [$([$($x:expr),+]),+ $(,)?] => {
        {
            let rows_data: Vec<Vec<f64>> = vec![$( vec![$($x as f64),+] ),+];
            let rows = rows_data.len();
            let cols = rows_data[0].len();
            Matrix {
                rows,
                cols,
                data: rows_data.into_iter().flatten().collect(),
            }
        }
    };
}

impl Matrix {
    fn get(&self, r: usize, c: usize) -> f64 { self.data[r * self.cols + c] }
}

fn main() {
    let v = my_vec![1, 2, 3, 4, 5];
    println!("my_vec: {:?}", v);

    let v2: Vec<&str> = my_vec!["hello", "world",];  // trailing comma ok
    println!("my_vec str: {:?}", v2);

    let mut s = hashset![1, 2, 3, 2, 1];
    s.insert(4);
    println!("hashset size: {} (no dups)", s.len());

    let m = hashmap! {
        "one" => 1,
        "two" => 2,
        "three" => 3,
    };
    println!("hashmap: {:?}", m["two"]);

    let d = deque![10, 20, 30];
    println!("deque front: {:?}", d.front());

    let mat = matrix![
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
    ];
    println!("matrix[1][2] = {}", mat.get(1, 2)); // 6.0
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_my_vec() {
        let v = my_vec![1, 2, 3];
        assert_eq!(v, vec![1, 2, 3]);
    }

    #[test]
    fn test_hashset() {
        let s = hashset![1, 2, 3, 2, 1];
        assert_eq!(s.len(), 3);
    }

    #[test]
    fn test_hashmap() {
        let m = hashmap!["a" => 1, "b" => 2];
        assert_eq!(m["a"], 1);
    }
}
(* vec!-like macros in OCaml โ€” collection literals *)

(* OCaml has literal list syntax; simulate for other structures *)

module type Collection = sig
  type 'a t
  val empty : 'a t
  val add : 'a -> 'a t -> 'a t
  val to_list : 'a t -> 'a list
end

module SimpleSet = struct
  type 'a t = 'a list
  let empty = []
  let add x s = if List.mem x s then s else x :: s
  let to_list x = List.sort compare x
end

(* Simulate a "macro" using a variadic function *)
let set_of items =
  List.fold_left (fun s x -> SimpleSet.add x s) SimpleSet.empty items

let queue_of items =
  let q = Queue.create () in
  List.iter (Queue.add q) items;
  q

let () =
  let s = set_of [3;1;4;1;5;9;2;6;5;3;5] in
  Printf.printf "Set: [%s]\n"
    (String.concat "; " (List.map string_of_int (SimpleSet.to_list s)));
  let q = queue_of [1;2;3;4;5] in
  Printf.printf "Queue size: %d\n" (Queue.length q)