Rust HashSet

A HashSet stores unique values with no repeats. Imagine a guest list at the door of an event: a name appears once, and the host checks it in a moment. Sets answer the question "is this item present?" extremely fast, and they remove duplicates without extra effort.

Creating a HashSet

The type lives in the standard collections module. Bring it into scope with a use line.

use std::collections::HashSet;

fn main() {
    let mut colors: HashSet<&str> = HashSet::new();
    colors.insert("red");
    colors.insert("green");
    colors.insert("red");   // duplicate, ignored
    println!("{}", colors.len());
}
2
 insert "red"   --> { red }
 insert "green" --> { red, green }
 insert "red"   --> { red, green }   (no change)

The Return Value of insert

The insert method returns a bool. A true value means the item was new, and false means the set already held it.

let mut seen = HashSet::new();
println!("{}", seen.insert(10));  // true
println!("{}", seen.insert(10));  // false

Building from a Vector

A set removes duplicates from a list in one step.

let numbers = vec![3, 1, 3, 2, 1, 5];
let unique: HashSet<i32> = numbers.into_iter().collect();
println!("{}", unique.len());   // 4

Shortcut for small sets: HashSet::from([1, 2, 3]).

Checking and Removing

let mut fruits = HashSet::from(["apple", "mango", "pear"]);

println!("{}", fruits.contains("mango"));  // true
fruits.remove("mango");
println!("{}", fruits.contains("mango"));  // false

Looping Through a Set

A set has no fixed order. The loop may print items in a different sequence on each run. Sort the items first when order matters.

let mut items: Vec<_> = fruits.iter().collect();
items.sort();
for item in items {
    println!("{item}");
}

Set Math

Sets support the same operations taught in school math. Two class rosters show the idea.

let math: HashSet<&str> = HashSet::from(["Anu", "Ben", "Cara"]);
let art: HashSet<&str> = HashSet::from(["Ben", "Dev", "Cara"]);
   math             art
 +---------+     +---------+
 |  Anu    |     |   Dev   |
 |    +----+-----+---+     |
 |    | Ben, Cara |        |
 |    +----+-----+---+     |
 +---------+     +---------+

Intersection

The intersection holds items present in both sets.

let both: Vec<_> = math.intersection(&art).collect();
// Ben, Cara (any order)

Union

The union holds every item from either set, without repeats.

let all: HashSet<_> = math.union(&art).collect();
println!("{}", all.len());   // 4

Difference

The difference holds items in the first set that the second set lacks.

let only_math: Vec<_> = math.difference(&art).collect();
// Anu

Symmetric Difference

The symmetric difference keeps items that appear in exactly one set. Here the result holds Anu and Dev.

Which Types Fit in a Set

Stored items need the Eq and Hash traits. Integers, strings, booleans, and characters qualify. Floating-point numbers do not qualify, because NaN breaks equality rules. Custom structs qualify after adding a derive line.

#[derive(Debug, Hash, PartialEq, Eq)]
struct Tag {
    name: String,
}

A Practical Example: Finding Duplicates

fn first_duplicate(values: &[i32]) -> Option<i32> {
    let mut seen = HashSet::new();
    for &v in values {
        if !seen.insert(v) {
            return Some(v);
        }
    }
    None
}

fn main() {
    println!("{:?}", first_duplicate(&[4, 8, 15, 8, 16]));
}
Some(8)

BTreeSet for Sorted Output

The BTreeSet type keeps items in sorted order automatically. It runs slightly slower than HashSet, yet the loop output stays predictable.

use std::collections::BTreeSet;

let sorted: BTreeSet<i32> = [5, 1, 4, 1, 3].into_iter().collect();
println!("{:?}", sorted);   // {1, 3, 4, 5}

Common Mistakes

Expecting a fixed print order leads to confusing test failures. The internal layout changes between runs, so sort the items or choose BTreeSet when order matters.

Storing floating-point numbers fails, because floats do not implement Eq and Hash. Convert values to integers, or wrap them in a type that defines equality clearly.

Forgetting that insert keeps the original item can surprise you. When a new value equals an existing one, the set keeps the old one.

Real-World Uses

  • Removing duplicate email addresses from a signup list
  • Tracking visited pages while exploring a website
  • Checking which tags two articles share
  • Finding students enrolled in one course but not another
  • Detecting repeated values in a stream of data

Practice Ideas

  • Count the unique words in a sentence after converting each word to lowercase.
  • Find the common friends of two people stored as sets of names.
  • Check whether two lists hold the same items regardless of order.

Performance Notes

Insertion, lookup, and removal take roughly constant time on average, even for sets with millions of items. A vector needs a full scan for the same lookup. Choose a set whenever your code repeatedly asks whether an item exists.

Key Points

  • A HashSet stores each value once and checks membership quickly.
  • insert reports whether the item was new.
  • Union, intersection, and difference compare two sets.
  • Iteration order is not guaranteed; use BTreeSet for sorted order.