Count Data with HashMap

RustBeginner
Practice Now

Introduction

A vector organizes values by numeric position. A HashMap<K, V> instead associates each unique key of type K with one value of type V, which makes it useful for counts, settings, and other keyed data.

You will build a small word-count map, retrieve and replace a count, use the entry API for existing and missing words, and iterate over all values without relying on the map's unspecified key order.

Insert and Retrieve Keyed Values

In this step, you will insert two key-value pairs and safely retrieve one count.

Enter the project and open the source:

cd /home/labex/project/word-counts
nano src/main.rs

The prepared path std::collections::HashMap identifies HashMap inside Rust's standard-library collection module. The use line gives that long path the short local name HashMap. Treat this as prepared import plumbing for now; paths and imports are taught fully in the later modules Lab. HashMap<String, u32> stores owned string keys and unsigned integer counts. Replace the Step 1a comment with:

    let _ = counts.insert(String::from("rust"), 1);
    let _ = counts.insert(String::from("safe"), 1);

insert returns any value previously stored for that key. These keys are new, so the result is None. The pattern let _ = ...; evaluates the operation and deliberately discards its result. This bare _ is different from a name such as _returned_id: the bare wildcard does not create a binding you can use later.

Replace the Step 1b comment with:

    match counts.get("rust") {
        Some(count) => println!("Rust count: {count}"),
        None => println!("Rust count: missing"),
    }

get accepts the borrowed text "rust" and returns an Option<&u32> without moving the key or count out of the map. Save and exit, then check and run:

cargo check
cargo run --quiet
Unique words: 2
Rust count: 1

The first line proves the keys are unique; the second proves the lookup found the stored count.

Replace an Existing Count

In this step, you will use insert with an existing key and inspect the value that was replaced.

Open the source:

nano src/main.rs

Replace the Step 2 comment with:

    let previous = counts.insert(String::from("rust"), 2);
    println!("Previous rust count: {previous:?}");

A map keeps only one value per key. The new value 2 replaces 1, and the returned Some(1) makes that replacement observable. Save and exit, then run:

cargo run --quiet

The new line should be:

Previous rust count: Some(1)

The unique-word count remains two because replacement does not create another key.

Update Counts with the Entry API

In this step, you will increment one existing word and one missing word through the same API.

entry(key).or_insert(default) returns a mutable reference to the key's existing value, or inserts the default and returns a mutable reference to it. Open the source:

nano src/main.rs

Replace the Step 3 comment with:

    *counts.entry(String::from("safe")).or_insert(0) += 1;
    *counts.entry(String::from("fast")).or_insert(0) += 1;
    println!("Safe count: {}", counts["safe"]);
    println!("Fast count: {}", counts["fast"]);

Read the compact update from the inside out. First, entry(key) selects the place associated with the key, whether occupied or vacant. Then or_insert(0) ensures a value exists and returns a mutable reference, &mut u32, to it.

The leading * reaches the u32 behind that reference. Finally, += 1 adds one and stores the new count back in the map.

Therefore safe becomes 2, while missing fast starts at zero and becomes 1. Square brackets then read a value by key. Map indexing would panic if the key were missing; it is safe here only because the two preceding entry calls guarantee that both keys exist. Continue to use get when absence is possible.

Save and exit, then run:

cargo run --quiet

The final lines should now be:

Safe count: 2
Fast count: 1

One operation handled both the occupied and vacant-key cases.

Iterate Without Depending on Key Order

In this step, you will iterate over all stored counts and compute an order-independent total.

Hash maps do not promise a stable key iteration order, so beginner-friendly verification should not expect keys to print in a particular sequence. The values() method visits every stored value, which is sufficient for a total.

Open the source and replace the Step 4 comment:

nano src/main.rs
    let mut total = 0;
    for count in counts.values() {
        total += count;
    }
    println!("Total occurrences: {total}");

Here count is a shared reference, and += can add the referenced integer. Save and exit, then check and run:

cargo check
cargo run --quiet

The last line should be:

Total occurrences: 5

The total combines rust = 2, safe = 2, and fast = 1 without relying on their traversal order.

Summary

You inserted, retrieved, replaced, and incremented keyed counts, then iterated over all map values without assuming a stable key order.