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 existinsert()when replacing a valueentry()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 foraso I can edit it."insert()= "Replace the envelope forawith 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."
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")returnsOption<&mut i32>Some(value)means the key existsvalueis 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)inserts0 - 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
| Step | Map 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.
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:
valueis&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.
Vecindexing is positionalHashMapindexing is key lookupHashMapdoes not support mutable[]updates
Comparisons
| Operation | Vec | HashMap |
|---|---|---|
| Access style | by numeric index | by key |
| Example read | vec[0] | map["a"] |
Mutable indexing with [] | Yes | No |
| Safe mutable access alternative | vec.get_mut(0) | map.get_mut("a") |
| Insert if missing | not applicable | entry(...).or_insert(...) |
get_mut vs insert vs entry
| Method | Use when | Returns |
|---|---|---|
get_mut(key) | key should already exist | Option<&mut V> |
insert(key, value) | replace or add a full value | Option<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.
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 mapmeans the map may be changed.- It does not mean
map[key]becomes writable. get_mut()returnsOption<&mut V>.entry().or_insert(...)returns&mut V.- Use
*valueto 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")returnsNone.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"]panicsmap.get_mut("missing")returnsNonemap.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.
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." ]
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.