Hash Maps and Hash Sets

Set Union, Intersection, and Difference

HashSet<T> supports the four fundamental mathematical set operations—union, intersection, difference, and symmetric difference—both as lazy non-allocating iterators (yielding &T) and as eager bitwise operators on borrowed sets (&a | &b, &a & &b, &a - &b, &a ^ &b) that return a new owned HashSet<T> when T: Clone.

Set Algebra Reference Table

Set A = {1, 2, 3}        Set B = {3, 4, 5}

Union (A ∪ B):                 {1, 2, 3, 4, 5}   (in A, in B, or in both)
Intersection (A ∩ B):          {3}               (in BOTH A and B)
Difference (A \ B):            {1, 2}            (in A, but NOT in B)
Symmetric Difference (A △ B):  {1, 2, 4, 5}      (in A or B, but NOT both)
Operation Lazy Iterator Method (Item = &T) Eager Operator (-> HashSet<T>, requires T: Clone)
Union ($A \cup B$) a.union(&b) &a | &b
Intersection ($A \cap B$) a.intersection(&b) &a & &b
Difference ($A \setminus B$) a.difference(&b) &a - &b
Symmetric Difference ($A \triangle B$) a.symmetric_difference(&b) &a ^ &b

Lazy Iterators vs. Eager Operators

Prefer the lazy iterator methods (.union(), .intersection(), .difference(), .symmetric_difference()) when you only need to iterate over the result, count matches, or collect into a Vec—they perform zero heap allocations:

use std::collections::HashSet;

fn main() {
    let a = HashSet::from([1, 2, 3]);
    let b = HashSet::from([3, 4, 5]);

    // 1. Lazy iterator methods (no intermediate HashSet allocated):
    let common_count = a.intersection(&b).count();
    println!("Common element count: {common_count}");

    // 2. Eager operator syntax (`&a | &b`, `&a & &b`, `&a - &b`, `&a ^ &b`):
    let union_set: HashSet<i32> = &a | &b;
    let inter_set: HashSet<i32> = &a & &b;
    let diff_set: HashSet<i32> = &a - &b;
    let sym_diff_set: HashSet<i32> = &a ^ &b;

    let mut u: Vec<i32> = union_set.into_iter().collect();
    let mut d: Vec<i32> = diff_set.into_iter().collect();
    let mut s: Vec<i32> = sym_diff_set.into_iter().collect();
    u.sort_unstable();
    d.sort_unstable();
    s.sort_unstable();

    println!("Union (A | B):                {u:?}");
    println!("Intersection (A & B):         {inter_set:?}");
    println!("Difference (A - B):           {d:?}");
    println!("Symmetric Difference (A ^ B): {s:?}");
}
Common element count: 1
Union (A | B):                [1, 2, 3, 4, 5]
Intersection (A & B):         {3}
Difference (A - B):           [1, 2]
Symmetric Difference (A ^ B): [1, 2, 4, 5]

In-Place Set Updates

When modifying an existing mut set_a: HashSet<T> in place without allocating a brand-new set:

  • In-place union ($A \leftarrow A \cup B$): set_a.extend(set_b)
  • In-place intersection ($A \leftarrow A \cap B$): set_a.retain(|x| set_b.contains(x))
  • In-place difference ($A \leftarrow A \setminus B$): set_a.retain(|x| !set_b.contains(x))