---
title: "18.6 HashMap"
description: "Storing key-value pairs in a HashMap: insert and get, the entry API for update-or-insert, and how ownership works for keys and values."
url: "https://learnrust.net/chapter-18/hashmap/"
last_updated: "2026-06-13"
---

# 18.6 HashMap

A `Vec` looks values up by position; a **`HashMap<K, V>`** looks them up by *key*. It stores key-value pairs, a phone book mapping names to numbers, a tally mapping words to counts, and lets you fetch a value by its key quickly. It's the third essential collection, and this lesson covers inserting, getting, the elegant `entry` API for "update or insert," and how ownership applies to keys and values.

## Creating, inserting, getting

`HashMap` isn't in the prelude (unlike `Vec` and `String`), so you bring it into scope with `use` (lesson [13.2](https://learnrust.net/chapter-13/defining-modules/)). Then `insert` adds a pair and `get` looks one up:

```rust
use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Red"), 50);

    let blue = scores.get("Blue");
    println!("{blue:?}");

    let purple = scores.get("Purple");
    println!("{purple:?}");
}
```

```
Some(10)
None
```

`HashMap<K, V>` is generic over the key type `K` and value type `V` (lesson [15.3](https://learnrust.net/chapter-15/generic-structs-and-enums/)); here it's inferred as `HashMap<String, i32>` from the first `insert`. `get` returns `Option<&V>`, `Some(&value)` if the key is present, `None` if not, exactly like `Vec::get` (lesson [18.2](https://learnrust.net/chapter-18/vec-creating-and-updating/)) and for the same reason: the key might not be there, so the type forces you to handle absence (lesson [11.3](https://learnrust.net/chapter-11/option/)). Inserting with a key that already exists *overwrites* the old value.

## The entry API: update or insert

A constant need with maps is "give me the value for this key, or insert a default if it's missing, then update it." Doing that with `get` and a `match` is clumsy; the **`entry` API** does it cleanly. `entry(key)` returns a handle to that slot, and `or_insert(default)` returns a mutable reference to the value, inserting the default first if the key was absent:

```rust
use std::collections::HashMap;

fn main() {
    let text = "the quick brown the lazy the";
    let mut counts: HashMap<&str, i32> = HashMap::new();

    for word in text.split_whitespace() {
        let count = counts.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{counts:?}");
}
```

```
{"the": 3, "quick": 1, "brown": 1, "lazy": 1}
```

(The order of a `HashMap`'s entries is unspecified and varies between runs, so your line may list the words differently; only the counts are meaningful.) This is the classic word-frequency counter, and the `entry` line is the heart of it. `counts.entry(word).or_insert(0)` means "for this word's slot, insert 0 if it's not there yet, and give me a mutable reference to the value either way." Then `*count += 1` increments through that reference. The first time a word appears, it's inserted as 0 then bumped to 1; later appearances find the existing count and bump it. One line replaces a get-check-insert-update dance, which is why `entry` is one of the most-used `HashMap` methods.

> **Key insight**
>
> `entry(key).or_insert(default)` is the "update-or-insert" idiom: it hands you a mutable reference to the value for a key, creating it with a default if needed. It's the right tool whenever you're accumulating into a map, counting, grouping, tallying, because it collapses "is the key present? if not, add it; then modify it" into a single expression. Reach for it instead of `get` followed by `insert` whenever you'd otherwise check-then-modify.

## Ownership of keys and values

The ownership rules (chapter 8) apply to what goes into a map. For types that are `Copy` (like `i32`), values are copied in. For owned types (like `String`), they're *moved* into the map, which then owns them:

```rust
use std::collections::HashMap;

fn main() {
    let name = String::from("Blue");
    let mut map = HashMap::new();
    map.insert(name, 10);
    // println!("{name}");   // refused: name was moved into the map
}
```

Inserting `name` (a `String`) moves it into `map`, so `name` can't be used afterward (lesson [8.4](https://learnrust.net/chapter-8/moves/)), the map now owns that text and will drop it when the map is dropped. If you need to keep using the key after inserting, insert a clone (lesson [8.7](https://learnrust.net/chapter-8/clone/)) or use references as keys (which brings lifetimes, chapter 17, into play, since the map must not outlive the borrowed keys). This is the same ownership story as everywhere else, here applied to collection contents: putting an owned value into a collection gives the collection ownership of it.

Notice that `get("Blue")` earlier took a `&str` even though the keys are `String`: looking up doesn't require an owned key, you can query with a borrowed form, which is the deref-coercion convenience from lesson [9.7](https://learnrust.net/chapter-9/string-slices/). You insert owned keys but can look up with borrowed ones.

## Quiz time

**Question #1**

What does `HashMap::get` return, and why isn't it just the value?

<details class="solution">
<summary>Show solution</summary>

It returns `Option<&V>`: `Some(&value)` if the key is present, `None` if not. It's an `Option` because the key might not be in the map, so the type forces you to handle the missing case (lesson [11.3](https://learnrust.net/chapter-11/option/)), just like `Vec::get`. It's a *reference* to the value (you're borrowing what the map owns), not the value itself.

</details>

**Question #2**

What does `map.entry(key).or_insert(0)` do, and why is it better than a `get`-then-`insert`?

<details class="solution">
<summary>Show solution</summary>

It returns a mutable reference to the value for `key`, inserting `0` first if the key wasn't present. It collapses "check if the key exists, insert a default if not, then get a mutable reference to modify" into one expression, which is exactly what you need when accumulating (counting, tallying) into a map. Doing it with `get` then `insert` is several clumsier steps.

</details>

**Question #3**

You insert a `String` key into a `HashMap` and then try to use that `String` variable. What happens, and why?

<details class="solution">
<summary>Show solution</summary>

It's refused: inserting a `String` *moves* it into the map (the map takes ownership), so the original variable can't be used afterward (lesson [8.4](https://learnrust.net/chapter-8/moves/), E0382). To keep using it, insert `key.clone()` instead, or use a borrowed key type with appropriate lifetimes. Putting an owned value into a collection transfers ownership to the collection.

</details>

You now have the three core collections. The next lesson revisits the fixed-size cousins, arrays and slices, in depth: when a fixed length beats a `Vec`, and why slices are the universal way to write functions that accept either.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
