Question

How to Update Values in a Rust HashMap

rusthashmap

Question

I am trying to update a value inside a mutable HashMap, but direct indexing does not work.

use std::collections::HashMap;

fn main() {
    let mut my_map = HashMap::new();
    my_map.insert("a", 1);
    my_map.insert("b", 3);

    my_map["a"] += 10;
    // I expect my_map to become {"a": 11, "b": 3}
}

However, Rust reports an error saying that the indexed content cannot be assigned to, even though the HashMap itself is mutable.

By comparison, this works with a vector:

fn main() {
    let mut my_vec = vec![1, 2, 3];

    my_vec[0] += 10;
    println!("{:?}", my_vec);
    // [11, 2, 3]
}

Why is HashMap different from Vec here, and what is the correct way to update a value in a Rust HashMap?

Short Answer

By the end of this page, you will understand why HashMap indexing in Rust can read values but not modify them, and how to update map values correctly using get_mut, insert, and the entry API. You will also see how this differs from Vec indexing and which approach to use in real code.

Concept

In Rust, a value being declared with mut does not mean every way of accessing it allows mutation. It only means you are allowed to mutate it through APIs that provide mutable access.

With Vec, indexing like my_vec[0] can return a mutable reference when used in a mutable context, because vectors support mutable indexing.

With HashMap, indexing works differently:

let value = my_map["a"];

This is designed for convenient read access. Internally, HashMap implements indexing in a way that gives you an immutable reference to the value. It does not implement mutable indexing for [], so this fails:

my_map["a"] += 10;

The reason is partly API design and safety. A hash map lookup is not the same as array indexing:

  • a vector index is a direct position
  • a hash map lookup depends on hashing and key lookup
  • a key might not exist
  • mutable indexing with [] would need different behavior and error expectations

So, to update a value in a HashMap, Rust expects you to use methods that clearly express your intent:

  • get_mut() when the key should already exist
  • insert() when replacing a value
  • entry() when you want to update-or-insert in one flow

This matters in real programming because maps are often used for counters, caches, configuration tables, and grouped data. Knowing the right API makes your code safer and clearer.

Mental Model

Think of a Vec like a row of numbered lockers. If you know locker 0, you can open it directly and change what is inside.

A HashMap is more like a reception desk with labeled envelopes. Asking for "a" through indexing is like saying, "Show me the envelope for a." Rust allows that as a read-only lookup.

If you want to change the contents, you need to ask for editable access using the proper process:

  • get_mut() = "Give me the envelope for a so I can edit it."
  • insert() = "Replace the envelope for a with this new one."
  • entry() = "If the envelope exists, update it; otherwise create it."

So mut on the map means "this map may be changed," not "every access path is automatically writable."

Take Quiz

Syntax and Examples

The most common ways to update a HashMap value are below.

1. Update an existing value with get_mut

use std::collections::HashMap;

fn main() {
    let mut my_map = HashMap::new();
    my_map.insert("a", 1);
    my_map.insert("b", 3);

    if let Some(value) = my_map.get_mut("a") {
        *value += 10;
    }

    println!("{:?}", my_map);
}

Why this works

  • get_mut("a") returns Option<&mut i32>
  • Some(value) means the key exists
  • value is a mutable reference, so you must dereference it with *value

2. Replace a value with insert

use std::collections::HashMap;

fn main() {
    let mut my_map = HashMap::new();
    my_map.insert("a", 1);

    my_map.insert("a", 11);

    println!("{:?}", my_map);
}

This replaces the old value for key "a".

3. Update or insert with entry

use std::collections::HashMap;

fn main() {
    let mut my_map = HashMap::new();

    *my_map.entry("a").or_insert(0) += 10;
    *my_map.entry("a").or_insert(0) += 1;

    println!("{:?}", my_map);
}

Why entry is useful

  • If "a" is missing, or_insert(0) inserts 0
  • It returns a mutable reference to the value
  • Then you can update it immediately

This is especially common for counters and frequency maps.

Step by Step Execution

Consider this example:

use std::collections::HashMap;

fn main() {
    let mut scores = HashMap::new();
    scores.insert("alice", 5);

    if let Some(score) = scores.get_mut("alice") {
        *score += 2;
    }

    println!("{:?}", scores);
}

Step by step

1. Create the map

let mut scores = HashMap::new();

An empty mutable HashMap is created.

2. Insert a key-value pair

scores.insert("alice", 5);

Now the map contains:

{"alice": 5}

3. Request mutable access to the value

scores.get_mut("alice")

This returns:

Some(&mut 5)

Conceptually, this means Rust found the value and gives you permission to modify it.

4. Match the Option

if let Some(score) = scores.get_mut("alice") {

This only enters the block if the key exists.

5. Dereference and update

*score += 2;

score is a mutable reference, not the integer itself. *score accesses the actual integer, changing it from 5 to 7.

6. Print the result

println!("{:?}", scores);

Output:

{"alice": 7}

Trace summary

StepMap contents
After creation{}
After insert{"alice": 5}
After get_mut update{"alice": 7}

Real World Use Cases

HashMap updates are very common in practical Rust programs.

Counting occurrences

use std::collections::HashMap;

fn main() {
    let words = vec!["cat", "dog", "cat", "bird", "dog", "cat"];
    let mut counts = HashMap::new();

    for word in words {
        *counts.entry(word).or_insert(0) += 1;
    }

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

Used for:

  • word frequency analysis
  • log event counting
  • API request statistics

Updating cached values

A map may store previously computed results. When new data arrives, you update the stored value.

Tracking user scores or balances

use std::collections::HashMap;

fn main() {
    let mut balances = HashMap::new();
    balances.insert("user_1", 100);

    if let Some(balance) = balances.get_mut("user_1") {
        *balance -= 20;
    }
}

Grouping data during processing

Maps are often updated while parsing files, processing CSV rows, or aggregating API responses.

Take Quiz

Real Codebase Usage

In real projects, developers usually avoid direct map indexing for updates and prefer clearer patterns.

Pattern: update if present

if let Some(value) = map.get_mut(key) {
    *value += 1;
}

Use this when the key should already exist.

Pattern: insert default then update

*map.entry(key).or_insert(0) += 1;

This is the standard pattern for counters.

Pattern: replace a computed value

map.insert(key, new_value);

Useful when you already know the full replacement value.

Pattern: guard clause for missing data

let Some(value) = map.get_mut(key) else {
    return;
};

*value += 1;

This keeps code flat and readable.

Pattern: validation before mutation

if let Some(age) = users.get_mut("alice") {
    if *age >= 0 {
        *age += 1;
    }
}

Why this style is preferred

  • handles missing keys safely
  • makes mutation explicit
  • avoids panics from assuming data exists
  • reads clearly in team codebases

Common Mistakes

1. Assuming mut makes every access mutable

This does not work:

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("a", 1);

    map["a"] += 1; // error
}

mut allows the map to be changed, but you still need a method that gives mutable access.

2. Forgetting to dereference get_mut() results

Broken code:

if let Some(value) = map.get_mut("a") {
    value += 1;
}

Why it fails:

  • value is &mut i32
  • you need to modify the value behind the reference

Correct code:

if let Some(value) = map.get_mut("a") {
    *value += 1;
}

3. Ignoring the possibility that a key is missing

Broken idea:

let value = map.get_mut("missing");

get_mut() returns an Option, because the key may not exist.

Correct approach:

if let Some(value) = map.get_mut("missing") {
    *value += 1;
}

Or use entry() if you want to create the key automatically.

4. Using insert when you only want to increment

This works, but may be awkward:

map.insert("a", 42);

If your goal is to update based on the old value, entry() or get_mut() is usually better.

5. Expecting [] on HashMap to behave like [] on Vec

These are not interchangeable APIs.

  • Vec indexing is positional
  • HashMap indexing is key lookup
  • HashMap does not support mutable [] updates

Comparisons

OperationVecHashMap
Access styleby numeric indexby key
Example readvec[0]map["a"]
Mutable indexing with []YesNo
Safe mutable access alternativevec.get_mut(0)map.get_mut("a")
Insert if missingnot applicableentry(...).or_insert(...)

get_mut vs insert vs entry

MethodUse whenReturns
get_mut(key)key should already existOption<&mut V>
insert(key, value)replace or add a full valueOption<V> old value
entry(key).or_insert(default)update existing or create missing&mut V

Which should you choose?

  • Use get_mut() when missing keys are normal and should be handled explicitly.
  • Use insert() when you already know the new value.
  • Use entry() when you want a concise update-or-create pattern.
Take Quiz

Cheat Sheet

Quick reference

Read a value by key

let v = my_map["a"];
  • good for reading
  • panics if the key does not exist
  • does not allow mutation

Mutate an existing value

if let Some(v) = my_map.get_mut("a") {
    *v += 1;
}

Replace a value

my_map.insert("a", 10);

Insert if missing, then update

*my_map.entry("a").or_insert(0) += 1;

Important rules

  • let mut map means the map may be changed.
  • It does not mean map[key] becomes writable.
  • get_mut() returns Option<&mut V>.
  • entry().or_insert(...) returns &mut V.
  • Use *value to modify through a mutable reference.

Common patterns

Counter

*counts.entry(word).or_insert(0) += 1;

Update only if present

if let Some(v) = map.get_mut(key) {
    *v = new_value;
}

Replace unconditionally

map.insert(key, new_value);

Edge cases

  • map["missing"] will panic.
  • get_mut("missing") returns None.
  • entry("missing").or_insert(default) creates the key if needed.

FAQ

Why can't I write my_map["a"] += 1 in Rust?

Because HashMap indexing provides read-only access for []. HashMap does not implement mutable indexing, so you must use get_mut() or entry().

Does let mut my_map mean the values are mutable too?

It means the map can be modified, but only through APIs that allow mutation. Mutability depends on how you access the data.

What should I use to increment a value in a HashMap?

Use get_mut() if the key already exists, or entry().or_insert(...) if the key may be missing.

Is insert() the same as updating a value?

It can be. If the key already exists, insert() replaces the old value with the new one.

Why does vector indexing allow mutation but HashMap indexing does not?

Vec supports mutable indexing because positions are direct and predictable. HashMap uses key lookup and has a different API design, so mutation is done through methods instead.

What happens if the key does not exist?

  • map["missing"] panics
  • map.get_mut("missing") returns None
  • map.entry("missing").or_insert(default) inserts a new value

Which method is most idiomatic for counters?

entry().or_insert(0) is the most common and idiomatic approach for counters in Rust.

Take Quiz

Mini Project

Description

Build a small Rust program that counts how many times each word appears in a sentence. This demonstrates the most practical HashMap update pattern: inserting a default value when a key is missing and then mutating the existing value.

Goal

Create a word frequency counter using a HashMap and update values safely with the entry API.

Requirements

[ "Split a sentence into words.", "Store word counts in a HashMap.", "Increment the count for each word.", "Print the final map of word frequencies." ]

Take Quiz

Keep learning

Related questions

View more