Skip to content

Intermediate · Error handling and collections · topic 26 of 50 · 30 min

HashMap and HashSet

Explanation

HashMap<K, V> stores key-value pairs and finds a value by its key very fast. insert adds or replaces, get returns an Option, and the entry API (map.entry(k).or_insert(0)) is the neat way to update counts. HashSet<T> stores unique values only. Neither keeps any order; use BTreeMap or BTreeSet when you need sorted keys.

Why a map?

Looking up a student's marks in a Vec of pairs means checking every pair until you find the name: slow for a big class. A hash map stores values by key, so lookup is about the same speed whether it holds 10 items or 10 million. A key is what you search by (a roll number, a city name); the value is what you get back.

rust
use std::collections::HashMap;

fn main() {
    let mut stock: HashMap<String, u32> = HashMap::new();
    stock.insert(String::from("rice"), 40);
    stock.insert(String::from("dal"), 25);
    stock.insert(String::from("rice"), 35);   // replaces 40

    println!("{:?}", stock.get("rice"));      // Some(35)
    println!("{:?}", stock.get("ghee"));      // None
    println!("{}", stock.len());              // 2
}
HashMap is not imported automatically; bring it in with use. get returns an Option.

Hashing means turning a key into a number that tells the map where to store the value. You do not need the details; just know that keys must support hashing and equality (types like String, integers, char and bool do).

Ownership in maps

insert moves owned values like String into the map; the map now owns them. Copy values like u32 are copied. get gives back a reference to the value inside the map, so the map keeps ownership.

The entry API: insert or update

A very common job is "if the key exists, update it; otherwise insert a starting value". The entry API does this in one step. entry(key) looks up the key, or_insert(start) inserts the start value if missing, and both return a mutable reference to the value.

rust
use std::collections::HashMap;

fn main() {
    let text = "chai samosa chai jalebi chai samosa";
    let mut counts: HashMap<&str, u32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }
    println!("{:?}", counts.get("chai"));   // Some(3)
}
The * dereferences the &mut u32 returned by or_insert, so += changes the count inside the map.

Looping and order

rust
for (item, qty) in &stock {
    println!("{item}: {qty}");
}
Gives (&key, &value) pairs.
Careful: A HashMap has no fixed order. Printing it twice in different runs may show a different order. If you need keys in sorted order, use std::collections::BTreeMap, which keeps keys sorted (lookups are a bit slower), or collect the keys into a Vec and sort it.

Other useful methods

MethodWhat it does
contains_key(&k)Whether the key exists
remove(&k)Remove and return Some(value), or None
get_mut(&k)A mutable reference to change a value in place
keys() / values()Iterate over just the keys or values
entry(k).or_insert_with(f)Insert a value computed by f only when missing

HashSet: unique items

A HashSet<T> is like a HashMap with keys only. It stores each value once and answers "is this in the set?" quickly. insert returns false if the value was already there, which is handy for spotting duplicates.

rust
use std::collections::HashSet;

fn main() {
    let mut seen = HashSet::new();
    for phone in ["98", "97", "98", "96"] {
        if !seen.insert(phone) {
            println!("duplicate: {phone}");    // duplicate: 98
        }
    }
    let a: HashSet<i32> = [1, 2, 3].into_iter().collect();
    let b: HashSet<i32> = [2, 3, 4].into_iter().collect();
    let common: HashSet<_> = a.intersection(&b).collect();
    println!("{}", common.len());              // 2
}
Sets also support union, intersection and difference.

Common mistakes

  • Forgetting use std::collections::HashMap;. The compiler error suggests the import.
  • Expecting insertion order when printing. Use BTreeMap or sort the keys.
  • Writing two steps (get, then insert) to update a count. Use entry(...).or_insert(...).
  • Using f64 as a key. Floats cannot be hashed in std because NaN is not equal to itself; use integers (like paise) or strings.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

HashMap and HashSet · 1/6

key → value

A HashMap stores key value pairs and finds a value by its key very fast, however big the map is.

Prefer a video by a teacher? Find free videos on “rust hashmap entry api hashset tutorial” on YouTube ↗

In real life: Counting UPI payments by app

A small shop exports its day's payments: each line has the app used (GPay, PhonePe, Paytm...) and the amount. A HashMap<String, (u32, u64)> keyed by app name counts payments and sums amounts in one pass using the entry API. The owner then sees which app customers use most, without caring about the order the lines came in.

rust
use std::collections::HashMap;

fn main() {
    let payments = [("PhonePe", 120), ("GPay", 80), ("PhonePe", 45), ("Paytm", 300), ("GPay", 60)];
    let mut by_app: HashMap<&str, (u32, u64)> = HashMap::new();
    for (app, amount) in payments {
        let entry = by_app.entry(app).or_insert((0, 0));
        entry.0 += 1;
        entry.1 += amount;
    }
    let mut apps: Vec<_> = by_app.into_iter().collect();
    apps.sort();                                   // sorted by app name
    for (app, (count, total)) in apps {
        println!("{app}: {count} payments, Rs {total}");
    }
}
Prints GPay: 2 payments, Rs 140, then Paytm: 1 payments, Rs 300, then PhonePe: 2 payments, Rs 165.

Practice (0/3 done)

Task 1 · Question

What does this print? use std::collections::HashMap; let mut m = HashMap::new(); m.insert("a", 1); m.insert("a", 5); *m.entry("b").or_insert(0) += 2; println!("{:?} {:?}", m.get("a"), m.get("b"));

Task 2 · Question

You need the keys printed in alphabetical order every time. Which type should you use?

Task 3 · Practice

Read a sentence and count how many times each word appears (ignore capital letters with to_lowercase()). Print the words sorted by count, highest first. Then use a HashSet to print the number of different words.

Quick quiz

Question 1

1.What does map.get(&key) return?

Question 2

2.What does entry(k).or_insert(0) return?

Question 3

3.Which statement about HashMap order is true?

Question 4

4.What does HashSet::insert return when the value is already present?

Question 5

5.Which key type cannot be used in a std HashMap?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What does map.get(&key) return?

Answer: An Option with a reference to the value

get returns Option<&V> because the key may be missing.

What does entry(k).or_insert(0) return?

Answer: A mutable reference to the value for k (inserting 0 if needed)

It returns &mut V, so you can update the value in place.

Which statement about HashMap order is true?

Answer: There is no guaranteed order

Iteration order of a HashMap is not specified.

What does HashSet::insert return when the value is already present?

Answer: false

insert returns false if the set already had the value.

Which key type cannot be used in a std HashMap?

Answer: f64

f64 does not implement Hash and Eq, because NaN is not equal to itself.

What does HashSet<T>.Add return for a value already in the set?Related

Answer: false

false means nothing was added.

From Collections: List, Dictionary, HashSet, Queue and Stack

What does map.get(key) return for a missing key in a HashMap?Related

Answer: null

Use getOrDefault to avoid null.

From Maps: HashMap, LinkedHashMap and TreeMap

Which call returns end() when the key is missing, without inserting?Related

Answer: m.find(key)

find only searches. at throws; [] inserts.

From std::map and std::unordered_map

What does set.add(x) return if x is already present?Related

Answer: false

add returns false when nothing changed.

From Sets: HashSet, LinkedHashSet and TreeSet

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Error handling and collections”. The module exam comes next.
  2. Module 6 exam: Error handling and collections · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Modules, crates, traits and generics. 50 topics left in the course before the final exam.

← Previous: Vec<T>: growable lists