417: Vec-like Collection Macros
Tutorial Video
Text description (accessibility)
This video demonstrates the "417: Vec-like Collection Macros" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. The `vec![1, 2, 3]` literal macro is so convenient that the absence of equivalent literals for `HashSet`, `BTreeSet`, and `HashMap` is a constant friction point. Key difference from OCaml: 1. **Built
Tutorial
The Problem
The vec![1, 2, 3] literal macro is so convenient that the absence of equivalent literals for HashSet, BTreeSet, and HashMap is a constant friction point. Initializing these collections requires let mut s = HashSet::new(); s.insert(1); s.insert(2); — three lines per item. Collection literal macros (set!, map!, btree_set!) bring the same ergonomics to all standard collections, making initialization as concise as vec! while maintaining type safety and supporting trailing commas.
Collection literal macros are so commonly needed that third-party crates like maplit and im provide them, and they're one of the most commonly written first macros.
🎯 Learning Outcomes
vec! works internally and how to replicate the pattern for other collections::std::collections::HashSet::new() fully-qualified paths prevent name resolution issues in macros$(,)?) improves ergonomic macro usageCode Example
// vec! is built-in
let v = vec![1, 2, 3];
// Custom macros for other collections
let s = set![1, 2, 3];
let m = map!{"a" => 1, "b" => 2};
let d = deque![1, 2, 3];Key Differences
vec! and equivalent macros are library code that expands to push calls.set!() explicitly (no elements means no insert calls); OCaml's Set.empty is a value.OCaml Approach
OCaml uses module functions for collection creation: let s = List.fold_left (fun acc x -> Set.add x acc) Set.empty [1; 2; 3]. Base.Set.of_list and Base.Map.of_alist_exn provide one-liner initialization. OCaml's list literal syntax [1; 2; 3] is built-in and convenient; other collections require explicit construction. No macro infrastructure is needed since the module functions are expressive enough.
Full Source
#![allow(clippy::all)]
//! Vec-like Collection Macros
//!
//! Creating collection literals with macros.
/// HashSet literal macro.
#[macro_export]
macro_rules! set {
() => { ::std::collections::HashSet::new() };
($($elem:expr),+ $(,)?) => {{
let mut s = ::std::collections::HashSet::new();
$(s.insert($elem);)+
s
}};
}
/// BTreeSet literal macro.
#[macro_export]
macro_rules! btree_set {
() => { ::std::collections::BTreeSet::new() };
($($elem:expr),+ $(,)?) => {{
let mut s = ::std::collections::BTreeSet::new();
$(s.insert($elem);)+
s
}};
}
/// HashMap literal macro.
#[macro_export]
macro_rules! map {
() => { ::std::collections::HashMap::new() };
($($k:expr => $v:expr),+ $(,)?) => {{
let mut m = ::std::collections::HashMap::new();
$(m.insert($k, $v);)+
m
}};
}
/// VecDeque literal macro.
#[macro_export]
macro_rules! deque {
() => { ::std::collections::VecDeque::new() };
($($elem:expr),+ $(,)?) => {{
let mut d = ::std::collections::VecDeque::new();
$(d.push_back($elem);)+
d
}};
}
/// LinkedList literal macro.
#[macro_export]
macro_rules! list {
() => { ::std::collections::LinkedList::new() };
($($elem:expr),+ $(,)?) => {{
let mut l = ::std::collections::LinkedList::new();
$(l.push_back($elem);)+
l
}};
}
/// BinaryHeap (max-heap) literal macro.
#[macro_export]
macro_rules! heap {
() => { ::std::collections::BinaryHeap::new() };
($($elem:expr),+ $(,)?) => {{
let mut h = ::std::collections::BinaryHeap::new();
$(h.push($elem);)+
h
}};
}
/// Vec with transformation.
#[macro_export]
macro_rules! vec_map {
($f:expr; $($elem:expr),* $(,)?) => {
vec![$($f($elem)),*]
};
}
/// Vec with filter.
#[macro_export]
macro_rules! vec_filter {
($pred:expr; $($elem:expr),* $(,)?) => {{
let pred = $pred;
let mut v = Vec::new();
$(if pred(&$elem) { v.push($elem); })*
v
}};
}
#[cfg(test)]
mod tests {
use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
#[test]
fn test_set_empty() {
let s: HashSet<i32> = set!();
assert!(s.is_empty());
}
#[test]
fn test_set_elements() {
let s = set![1, 2, 3, 2, 1];
assert_eq!(s.len(), 3);
assert!(s.contains(&1));
assert!(s.contains(&2));
assert!(s.contains(&3));
}
#[test]
fn test_btree_set() {
let s = btree_set![3, 1, 2];
let v: Vec<_> = s.into_iter().collect();
assert_eq!(v, vec![1, 2, 3]); // sorted
}
#[test]
fn test_map_empty() {
let m: HashMap<&str, i32> = map!();
assert!(m.is_empty());
}
#[test]
fn test_map_entries() {
let m = map! {
"a" => 1,
"b" => 2,
};
assert_eq!(m["a"], 1);
assert_eq!(m["b"], 2);
}
#[test]
fn test_deque() {
let d = deque![1, 2, 3];
assert_eq!(d.len(), 3);
assert_eq!(d.front(), Some(&1));
assert_eq!(d.back(), Some(&3));
}
#[test]
fn test_list() {
let l = list![1, 2, 3];
assert_eq!(l.len(), 3);
}
#[test]
fn test_heap() {
let mut h = heap![3, 1, 4, 1, 5];
assert_eq!(h.pop(), Some(5)); // max first
assert_eq!(h.pop(), Some(4));
}
#[test]
fn test_vec_map() {
let v = vec_map!(|x| x * 2; 1, 2, 3);
assert_eq!(v, vec![2, 4, 6]);
}
#[test]
fn test_vec_filter() {
let v = vec_filter!(|x: &i32| *x % 2 == 0; 1, 2, 3, 4, 5);
assert_eq!(v, vec![2, 4]);
}
}#[cfg(test)]
mod tests {
use std::collections::{BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque};
#[test]
fn test_set_empty() {
let s: HashSet<i32> = set!();
assert!(s.is_empty());
}
#[test]
fn test_set_elements() {
let s = set![1, 2, 3, 2, 1];
assert_eq!(s.len(), 3);
assert!(s.contains(&1));
assert!(s.contains(&2));
assert!(s.contains(&3));
}
#[test]
fn test_btree_set() {
let s = btree_set![3, 1, 2];
let v: Vec<_> = s.into_iter().collect();
assert_eq!(v, vec![1, 2, 3]); // sorted
}
#[test]
fn test_map_empty() {
let m: HashMap<&str, i32> = map!();
assert!(m.is_empty());
}
#[test]
fn test_map_entries() {
let m = map! {
"a" => 1,
"b" => 2,
};
assert_eq!(m["a"], 1);
assert_eq!(m["b"], 2);
}
#[test]
fn test_deque() {
let d = deque![1, 2, 3];
assert_eq!(d.len(), 3);
assert_eq!(d.front(), Some(&1));
assert_eq!(d.back(), Some(&3));
}
#[test]
fn test_list() {
let l = list![1, 2, 3];
assert_eq!(l.len(), 3);
}
#[test]
fn test_heap() {
let mut h = heap![3, 1, 4, 1, 5];
assert_eq!(h.pop(), Some(5)); // max first
assert_eq!(h.pop(), Some(4));
}
#[test]
fn test_vec_map() {
let v = vec_map!(|x| x * 2; 1, 2, 3);
assert_eq!(v, vec![2, 4, 6]);
}
#[test]
fn test_vec_filter() {
let v = vec_filter!(|x: &i32| *x % 2 == 0; 1, 2, 3, 4, 5);
assert_eq!(v, vec![2, 4]);
}
}
Deep Comparison
OCaml vs Rust: Collection Literal Macros
Rust Collection Macros
// vec! is built-in
let v = vec![1, 2, 3];
// Custom macros for other collections
let s = set![1, 2, 3];
let m = map!{"a" => 1, "b" => 2};
let d = deque![1, 2, 3];
OCaml Collection Literals
(* Lists are literal *)
let l = [1; 2; 3]
(* Sets/Maps via modules *)
let s = IntSet.(empty |> add 1 |> add 2 |> add 3)
let m = StringMap.(empty |> add "a" 1 |> add "b" 2)
(* Or via List.to_seq *)
let s = IntSet.of_list [1; 2; 3]
5 Takeaways
vec! is built-in; others need custom macros.**set![1, 2, 3].**$(,)? makes them optional.**Exercises
omap!{ key => val, ... } creating a BTreeMap (ordered map). Verify that iteration produces keys in sorted order.default_map!{ key => val, ... ; default: expr } that creates a HashMap with a default value, returning the default for missing keys via a get_or_default(key) method on a wrapper.multimap!{ key1 => [v1, v2], key2 => [v3] } creating a HashMap<K, Vec<V>> where multiple values per key are supported via list syntax.