ExamplesBy LevelBy TopicLearning Paths
417 Fundamental

417: Vec-like Collection Macros

Functional Programming

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

  • • Understand how vec! works internally and how to replicate the pattern for other collections
  • • Learn how ::std::collections::HashSet::new() fully-qualified paths prevent name resolution issues in macros
  • • See how trailing comma support ($(,)?) improves ergonomic macro usage
  • • Understand how to handle both empty and non-empty collection initialization
  • • Learn how collection literal macros compose with type inference
  • Code 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

  • Built-in vs. macro: OCaml's list literal is syntax; Rust's vec! and equivalent macros are library code that expands to push calls.
  • Empty case: Rust macros must handle set!() explicitly (no elements means no insert calls); OCaml's Set.empty is a value.
  • Type inference: Rust's collection macros infer element types from the provided values; OCaml's typed modules require the element type to be known from the module.
  • Homogeneity: Both Rust and OCaml collection literals require all elements to have the same type; Rust enforces this via the type system, OCaml via the module's type parameter.
  • 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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.**
  • OCaml uses module functions, not literals.
  • **Macros enable natural syntax: set![1, 2, 3].**
  • All standard collections can have literal macros.
  • **Trailing commas: $(,)? makes them optional.**
  • Exercises

  • Ordered map: Implement omap!{ key => val, ... } creating a BTreeMap (ordered map). Verify that iteration produces keys in sorted order.
  • Default dict: Implement 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: Implement multimap!{ key1 => [v1, v2], key2 => [v3] } creating a HashMap<K, Vec<V>> where multiple values per key are supported via list syntax.
  • Open Source Repos