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
- Collection literals: `hashmap!`, `hashset!`, `deque!` โ write once, use like built-ins.
- DSL syntax: Custom repetition patterns for configuration, query building, test fixtures.
- Zero runtime cost: Macros expand at compile time to plain Rust code โ no overhead over hand-written loops.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| List literal | `[1; 2; 3]` built-in | `vec![1, 2, 3]` via macro |
| Custom syntax | PPX (preprocessor extensions) | `macro_rules!` declarative macros |
| Repetition pattern | Camlp4/PPX sequence matching | `$($x:expr),` + `$(body)` |
| Compile-time expansion | PPX (complex setup) | `macro_rules!` โ built into language |
| Map literal | No standard literal | Write `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)