Skip to content
AgileLogics
5 min read

HashMap & HashSet

Prerequisites: Error handling with Result

HashMap<K, V> stores values looked up by key; HashSet<T> stores unique values. Both live in std::collections and must be imported with use. Keys (and set members) must implement Eq and Hash, which strings, integers, char, bool and tuples of them already do.

Iteration order is unspecified. Rust's default hasher is randomly seeded, so looping over the same map can print entries in a different order on every run. If you need a stable order, sort the entries first, or use BTreeMap / BTreeSet, which keep keys sorted.

Syntax

use std::collections::{HashMap, HashSet};

fn main() {
    let mut ages: HashMap<String, u32> = HashMap::new();
    ages.insert(String::from("Ada"), 36);         // insert or overwrite; returns the old value
    let _ = ages.get("Ada");                      // Option<&u32>
    let _ = ages.contains_key("Bob");             // false
    let _ = ages.remove("Ada");                   // Option<u32>

    *ages.entry(String::from("Bob")).or_insert(0) += 1; // insert a default if missing, then modify

    let mut seen: HashSet<i32> = HashSet::new();
    let _ = seen.insert(3);                       // true: newly added
    let _ = seen.insert(3);                       // false: already present
}

The entry API

map.entry(key) looks the key up once and returns an Entry that is either occupied or vacant. or_insert(default) returns a mutable reference to the value, inserting default first if the key was missing. It replaces the clumsy "check, then insert, then look up again" pattern. or_default() does the same using the value type's Default (0 for numbers, empty for Vec/String).

Example

use std::collections::{HashMap, HashSet};

fn main() {
    let text = "the cat sat on the mat the end";

    // Count words.
    let mut counts: HashMap<&str, u32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }

    // Sort for a predictable order: highest count first, then alphabetically.
    let mut pairs: Vec<(&str, u32)> = counts.into_iter().collect();
    pairs.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
    for (word, n) in &pairs {
        println!("{word}: {n}");
    }

    // Group words by their first letter.
    let mut by_letter: HashMap<char, Vec<&str>> = HashMap::new();
    for word in ["apple", "avocado", "banana", "blueberry", "cherry"] {
        let first = word.chars().next().unwrap();
        by_letter.entry(first).or_default().push(word);
    }
    println!("b -> {:?}", by_letter[&'b']);

    // Set operations.
    let rust: HashSet<&str> = ["alice", "bob", "carol"].into_iter().collect();
    let go: HashSet<&str> = ["bob", "dave", "carol"].into_iter().collect();

    let mut both: Vec<_> = rust.intersection(&go).collect();
    both.sort();
    println!("both: {both:?}");

    let mut only_rust: Vec<_> = rust.difference(&go).collect();
    only_rust.sort();
    println!("only rust: {only_rust:?}");
}

Expected output

the: 3
cat: 1
end: 1
mat: 1
on: 1
sat: 1
b -> ["banana", "blueberry"]
both: ["bob", "carol"]
only rust: ["alice"]

Without the sort calls these lines would come out in a different order from run to run.

Common mistakes

  • Printing a map with {:?} or looping over it and expecting insertion order (or the same order each run). Sort, or use BTreeMap.
  • Indexing with map["key"] when the key might be missing. It panics; use map.get("key") and handle the None.
  • Inserting an owned String key and then being surprised it was moved: after map.insert(name, 1) you can no longer use name (borrow of moved value: `name` ). Clone it if you still need it.
  • Writing if !map.contains_key(k) { map.insert(k, 0); } *map.get_mut(k).unwrap() += 1;. It hashes the key three times; *map.entry(k).or_insert(0) += 1 does it once.
  • Using f64 as a key. Floats don't implement Eq or Hash, so error[E0599]: the method `insert` exists for struct `HashMap<f64, i32>`, but its trait bounds were not satisfied.

Practice

Given let votes = ["red", "blue", "red", "green", "blue", "red"];, count the votes with a HashMap and print each color and its count in alphabetical order. Then print how many distinct colors there were using a HashSet.

Show solution
use std::collections::{HashMap, HashSet};

fn main() {
    let votes = ["red", "blue", "red", "green", "blue", "red"];

    let mut counts: HashMap<&str, u32> = HashMap::new();
    for color in votes {
        *counts.entry(color).or_insert(0) += 1;
    }

    let mut colors: Vec<_> = counts.keys().collect();
    colors.sort();
    for color in colors {
        println!("{color}: {}", counts[color]);
    }

    let distinct: HashSet<&str> = votes.into_iter().collect();
    println!("distinct colors: {}", distinct.len());
}
blue: 2
green: 1
red: 3
distinct colors: 3