Rust 31 🦀 Key-Value HashMaps (HashMap)
A HashMap<K, V> stores a mapping from keys of type K to values of type V. It is the standard associative collection in Rust, backed by a hash table, and it provides average O(1) lookup, insertion, and deletion. It is the collection for the case where the relationship is “look up a value by its key” rather than “store a sequence of items.” The map is not ordered. If iteration order matters, a BTreeMap is the alternative, but the HashMap is the default when the order does not matter and the lookups are frequent.
This chapter covers HashMap<K, V> in full. You will learn how to create a map, insert and retrieve values, handle missing keys with Option and the entry API, update values in place, iterate over keys and values, and the ownership rules that govern the keys and values. You will also see the Hash and Eq bounds that make a type usable as a key and the patterns that use the map for counting, grouping, and caching.
Key point: A HashMap<K, V> requires that K implements Hash and Eq. The keys are unique; inserting a value with an existing key replaces the old value. The get method returns Option<&V> because the key may not be present. The entry API provides a way to insert a value only if the key is absent, or to modify the value if it is present. The map owns its keys and values; inserting a value moves it into the map.
Why HashMap exists
The lookup problem. A Vec<T> stores a sequence, and finding an element requires scanning the sequence. For a small collection, the scan is fast. For a large collection, it is O(n). A HashMap provides O(1) average lookup by key. The key is hashed to a bucket, and the value is found in the bucket. The trade-off is the memory overhead of the hash table and the requirement that the key implements Hash and Eq.
The association problem. Some data is naturally a mapping: a username to a user record, a word to its count, a configuration key to its value. The HashMap models the relationship directly. The Vec models a sequence, which is a different shape.
The uniqueness problem. A HashMap enforces unique keys. Inserting a value with an existing key replaces the old value. The map has at most one value per key. This is the invariant that makes the lookup meaningful.
The performance problem. The average case for the HashMap operations is O(1). The worst case is O(n) when the hash function produces many collisions, but the default hasher is SipHash, which is resistant to collision attacks and produces good distribution for most inputs. The standard library uses SipHash by default, and a different hasher can be provided if the performance profile requires it.
The ownership problem. The HashMap owns its keys and values. Inserting a value moves it into the map. Retrieving a value returns a reference, not a copy. The map is the owner, and the references borrow from it.
The entry problem. The common pattern is “insert if absent, update if present.” The entry API expresses this without a double lookup. The entry(key) returns an Entry that is either Occupied or Vacant, and the methods on the entry handle both cases.
a. Creating and populating a HashMap
A HashMap is created with HashMap::new or HashMap::with_capacity. It is not in the prelude, so it must be imported.
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
The HashMap::new creates an empty map. The insert method adds a key-value pair. The map infers the types from the first insert: HashMap<String, i32>.
The HashMap::with_capacity pre-allocates space for a known number of entries.
let mut scores = HashMap::with_capacity(10);
The collect method builds a map from an iterator of tuples.
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.into_iter()
.zip(initial_scores.into_iter())
.collect();
The zip pairs the teams with the scores, and the collect gathers the pairs into a map. The type annotation HashMap<_, _> tells the compiler the target type .
A map can be created from an array of tuples.
let scores: HashMap<&str, i32> = [
("Blue", 10),
("Yellow", 50),
].into_iter().collect();
The into_iter on the array produces tuples, and the collect gathers them into a map.
The keys and values are owned by the map. Inserting a String moves the string into the map.
let key = String::from("Blue");
let value = 10;
let mut map = HashMap::new();
map.insert(key, value);
// key is moved, cannot be used
// value is copied (i32 is Copy)
The key is moved because String is not Copy. The value is copied because i32 is Copy. After the insert, key is no longer valid, but value is.
b. Accessing and updating values
The get method returns Option<&V>.
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
let score = scores.get("Blue");
match score {
Some(s) => println!("Score: {}", s),
None => println!("No score"),
}
The get takes a reference to the key and returns Option<&V>. The key can be a &str even if the map’s key type is String, because String implements Borrow<str>.
let score = scores.get("Blue").copied().unwrap_or(0);
The .copied() converts Option<&i32> to Option<i32>, and the .unwrap_or(0) provides a default.
The contains_key method checks whether a key is present.
if scores.contains_key("Blue") {
println!("Blue is present");
}
The remove method removes a key-value pair and returns the value.
let removed = scores.remove("Blue");
println!("{:?}", removed); // Some(10)
The remove returns Option<V> because the key may not be present.
The insert method returns the previous value if the key was present.
let mut scores = HashMap::new();
scores.insert("Blue", 10);
let old = scores.insert("Blue", 25);
println!("{:?}", old); // Some(10)
println!("{:?}", scores.get("Blue")); // Some(&25)
The second insert replaces the value and returns the old one. The map has one value per key.
The entry API handles the “insert if absent, update if present” pattern.
let mut scores = HashMap::new();
scores.entry("Blue").or_insert(10);
scores.entry("Yellow").or_insert(50);
scores.entry("Blue").or_insert(25); // no effect, Blue exists
The or_insert inserts the value only if the key is absent. The third call does nothing because Blue is present.
The or_insert returns a mutable reference to the value, which can be modified.
let text = "hello world hello";
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let count = counts.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", counts); // {"hello": 2, "world": 1}
The entry(word).or_insert(0) returns a &mut i32 to the count, inserting 0 if the word is new. The *count += 1 increments the value. This is the standard word-count pattern .
The and_modify method modifies the value if the key is present.
scores.entry("Blue")
.and_modify(|e| *e += 10)
.or_insert(0);
The and_modify applies the closure to the value if the key exists, and the or_insert inserts 0 if it does not.
c. Iteration, ownership, and the key bounds
The HashMap can be iterated in several ways.
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
The &scores iterates by reference, and the items are (&K, &V). The map is not consumed.
for (key, value) in &mut scores {
*value += 1;
}
The &mut scores iterates by mutable reference, and the items are (&K, &mut V). The values can be modified.
for (key, value) in scores {
println!("{}: {}", key, value);
}
The scores without a reference iterates by value, and the items are (K, V). The map is consumed, and the keys and values are moved out.
The keys and values methods return iterators over the keys and the values.
for key in scores.keys() {
println!("{}", key);
}
for value in scores.values() {
println!("{}", value);
}
The keys returns Iterator<Item = &K>, and the values returns Iterator<Item = &V>.
The values_mut returns Iterator<Item = &mut V>.
for value in scores.values_mut() {
*value += 1;
}
The keys of a HashMap must implement Hash and Eq. The types that implement these include the primitives (i32, u64, bool, char), the strings (String, &str), and the tuples and structs that derive them.
#[derive(Hash, Eq, PartialEq)]
struct User {
id: u64,
name: String,
}
let mut map = HashMap::new();
map.insert(User { id: 1, name: "Alice".into() }, 100);
The #[derive(Hash, Eq, PartialEq)] adds the implementations. The Hash is required for the hashing, and the Eq and PartialEq are required for the equality comparison when the hashes collide.
A key that does not implement Hash and Eq cannot be used in a HashMap.
struct Point {
x: f64,
y: f64,
}
// let mut map = HashMap::new();
// map.insert(Point { x: 1.0, y: 2.0 }, 10); // error: f64 does not implement Hash
The f64 does not implement Hash because NaN is not equal to itself. A struct with f64 fields cannot be used as a key unless the Hash implementation is provided manually with a consistent Eq.
The BTreeMap is the alternative when the key does not implement Hash but does implement Ord.
use std::collections::BTreeMap;
let mut map = BTreeMap::new();
map.insert(3, "three");
map.insert(1, "one");
map.insert(2, "two");
for (key, value) in &map {
println!("{}: {}", key, value);
}
// 1: one
// 2: two
// 3: three
The BTreeMap is ordered by key, and the iteration is in sorted order. The HashMap is unordered, and the iteration order is not specified.
Complete Example Session
// ============================================
// PART 1: CREATE AND INSERT
// ============================================
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// ============================================
// PART 2: GET
// ============================================
let score = scores.get("Blue");
println!("{:?}", score); // Some(&10)
// ============================================
// PART 3: GET WITH DEFAULT
// ============================================
let score = scores.get("Green").copied().unwrap_or(0);
println!("{}", score); // 0
// ============================================
// PART 4: INSERT RETURNS OLD VALUE
// ============================================
let old = scores.insert(String::from("Blue"), 25);
println!("{:?}", old); // Some(10)
// ============================================
// PART 5: ENTRY API
// ============================================
scores.entry(String::from("Blue")).or_insert(50); // no effect
scores.entry(String::from("Green")).or_insert(30); // inserts
// ============================================
// PART 6: WORD COUNT
// ============================================
let text = "hello world hello";
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let count = counts.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", counts); // {"hello": 2, "world": 1}
// ============================================
// PART 7: ITERATE BY REFERENCE
// ============================================
for (key, value) in &scores {
println!("{}: {}", key, value);
}
// ============================================
// PART 8: ITERATE BY MUTABLE REFERENCE
// ============================================
for value in scores.values_mut() {
*value += 1;
}
// ============================================
// PART 9: ITERATE BY VALUE
// ============================================
for (key, value) in scores {
println!("{}: {}", key, value);
}
// scores is consumed
// ============================================
// PART 10: STRUCT KEY WITH DERIVE
// ============================================
#[derive(Hash, Eq, PartialEq)]
struct User {
id: u64,
name: String,
}
let mut map = HashMap::new();
map.insert(User { id: 1, name: "Alice".into() }, 100);
The ten parts covered create and insert, get, get with default, insert returning the old value, the entry API, word count, iteration by reference, iteration by mutable reference, iteration by value, and a struct key with derive.
Quick Reference
Creation
| Method | Purpose |
|---|---|
HashMap::new() | Empty map |
HashMap::with_capacity(n) | Pre-allocated |
collect() | From an iterator of tuples |
Access
| Method | Returns |
|---|---|
get(&key) | Option<&V> |
get_mut(&key) | Option<&mut V> |
contains_key(&key) | bool |
len() | Number of entries |
is_empty() | bool |
Modification
| Method | Effect |
|---|---|
insert(k, v) | Insert, returns Option<V> |
remove(&key) | Remove, returns Option<V> |
entry(k) | Entry for the key |
or_insert(v) | Insert if absent, returns &mut V |
and_modify(f) | Modify if present |
Iteration
| Method | Yields |
|---|---|
&map | (&K, &V) |
&mut map | (&K, &mut V) |
map | (K, V) |
keys() | &K |
values() | &V |
values_mut() | &mut V |
Key Bounds
| Bound | Required For |
|---|---|
Hash | Hashing the key |
Eq | Comparing keys |
PartialEq | Equality checks |
Best Practices
✅ Do This:
// Use entry for insert-if-absent
map.entry(key).or_insert(0); // ✅
// Use entry for counting
*map.entry(word).or_insert(0) += 1; // ✅
// Use with_capacity when the size is known
let mut map = HashMap::with_capacity(100); // ✅
// Use get with a default
let value = map.get("key").copied().unwrap_or(0); // ✅
// Derive Hash and Eq for struct keys
#[derive(Hash, Eq, PartialEq)]
struct User { id: u64 } // ✅
// Use and_modify with or_insert
map.entry(key).and_modify(|v| *v += 1).or_insert(1); // ✅
// Borrow the key when the map has a String key
map.get("blue"); // &str borrowed as &String // ✅
❌ Don’t Do This:
// Don't use a key that does not implement Hash
struct Point { x: f64, y: f64 }
// map.insert(Point { x: 1.0, y: 1.0 }, 1); // error // ❌
// Don't expect the map to be ordered
for (k, v) in &map { } // order is not specified // ⚠️
// Don't double-lookup with contains_key and get
if map.contains_key("k") { let v = map.get("k"); } // ⚠️
// use if let Some(v) = map.get("k") instead
// Don't forget that insert moves the key
let key = String::from("k");
map.insert(key, 1);
// println!("{}", key); // error // ❌
// Don't use a mutable reference after the map is modified
let v = map.get_mut("k").unwrap();
map.insert("other", 1); // error // ❌
// Don't use reduce for counting
// Use entry().or_insert() instead // ⚠️
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Key not found | get returns None | Use unwrap_or |
| Key moved | insert takes ownership | Clone or pass reference |
| Order not preserved | HashMap is unordered | Use BTreeMap |
| Hash collision | Bad hash function | Use the default hasher |
f64 key error | Hash not implemented | Use a wrapper |
| Borrow conflict | Holding &mut across insert | Drop the reference |
| Entry API misuse | or_insert always evaluated | Use or_insert_with |
Real-World Examples
1. Word Count
let mut counts = HashMap::new();
for word in text.split_whitespace() {
*counts.entry(word).or_insert(0) += 1;
}
2. Group by Key
let mut groups: HashMap<String, Vec<Item>> = HashMap::new();
for item in items {
groups.entry(item.category.clone()).or_default().push(item);
}
3. Cache
let mut cache = HashMap::new();
let result = cache.entry(input).or_insert_with(|| compute(input));
4. Configuration
let config: HashMap<String, String> = env::vars().collect();
let value = config.get("PATH").cloned().unwrap_or_default();
5. Index by ID
let users: HashMap<u64, User> = users.into_iter().map(|u| (u.id, u)).collect();
6. Merge Two Maps
let mut a = HashMap::from([("x", 1)]);
let b = HashMap::from([("x", 2), ("y", 3)]);
for (k, v) in b {
*a.entry(k).or_insert(0) += v;
}
7. Remove and Return
let removed = map.remove("key");
8. Update If Present
map.entry("key").and_modify(|v| *v *= 2);
9. Check and Insert
if !map.contains_key("key") {
map.insert("key", "value");
}
// better: map.entry("key").or_insert("value");
10. Iterate with Index
for (i, (key, value)) in map.iter().enumerate() {
println!("{}: {} = {}", i, key, value);
}
Visual
HashMap Structure
┌─────────────────────────────────────────────────────────────┐
│ HASH MAP │
│ │
│ key "Blue" ──hash──▶ bucket 3 │
│ key "Yellow" ──hash──▶ bucket 7 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Bucket 0 │ │
│ │ Bucket 1 │ │
│ │ Bucket 2 │ │
│ │ Bucket 3 → ("Blue", 10) │ │
│ │ Bucket 4 │ │
│ │ ... │ │
│ │ Bucket 7 → ("Yellow", 50) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Lookup: hash the key, go to the bucket, compare. │
│ Average O(1). │
│ │
└─────────────────────────────────────────────────────────────┘
Entry API
┌─────────────────────────────────────────────────────────────┐
│ map.entry(key) │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Entry │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Occupied │ │ Vacant │ │ │
│ │ │ (key exists) │ │ (key absent) │ │ │
│ │ └────────┬────────┘ └────────┬────────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ .or_insert() .or_insert() │ │
│ │ (no effect) (inserts value) │ │
│ │ returns &mut V returns &mut V │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ One lookup, both cases handled. │
│ │
└─────────────────────────────────────────────────────────────┘
HashMap vs BTreeMap
┌─────────────────────────────────────────────────────────────┐
│ HASHMAP │
│ │
│ Lookup: O(1) average │
│ Order: unspecified │
│ Key bound: Hash + Eq │
│ Use: fast lookup, order irrelevant │
│ │
├─────────────────────────────────────────────────────────────┤
│ │
│ BTREEMAP │
│ │
│ Lookup: O(log n) │
│ Order: sorted by key │
│ Key bound: Ord │
│ Use: ordered iteration, range queries │
│ │
└─────────────────────────────────────────────────────────────┘
Ownership
┌─────────────────────────────────────────────────────────────┐
│ map.insert(String::from("Blue"), 10); │
│ │
│ The key "Blue" is moved into the map. │
│ The value 10 is copied (i32 is Copy). │
│ │
│ map.get("Blue") │
│ → Option<&i32> │
│ → borrows from the map │
│ │
│ for (k, v) in &map │
│ → k: &String, v: &i32 │
│ │
│ for (k, v) in map │
│ → k: String, v: i32 │
│ → the map is consumed │
│ │
└─────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Type | HashMap<K, V> |
| Import | std::collections::HashMap |
| Create | HashMap::new() |
| Insert | map.insert(k, v) |
| Get | map.get(&k) → Option<&V> |
| Contains | map.contains_key(&k) |
| Remove | map.remove(&k) → Option<V> |
| Entry | map.entry(k) |
| Insert if absent | .or_insert(v) |
| Key bounds | Hash + Eq |
| Iteration | &map, &mut map, map |
| Ordered alternative | BTreeMap |
Key takeaways:
- A
HashMap<K, V>stores a mapping from keys to values. It provides average O(1) lookup, insertion, and deletion. The map is not ordered; use aBTreeMapwhen the order matters. - The
getmethod returnsOption<&V>. The key may not be present, and theOptionforces the caller to handle the absence. Theunwrap_orprovides a default. - The
insertmethod replaces the old value and returns it. The map has one value per key. The old value is returned asOption<V>. - The
entryAPI handles the insert-or-update pattern. Theentry(key).or_insert(value)inserts the value if the key is absent and returns a mutable reference to the value. The pattern is the standard for counting and grouping. - The keys must implement
HashandEq. The primitives, the strings, and the derived structs implement them. Thef64does not, becauseNaNis not equal to itself. - The map owns its keys and values. Inserting moves the value into the map. Retrieving returns a reference. Iterating by value consumes the map.
- The
BTreeMapis the ordered alternative. It uses theOrdbound instead ofHashandEq, and the iteration is in sorted order.
Remember: The HashMap is the default associative collection in Rust. Use it when the relationship is “look up a value by its key” and the order does not matter. Use the entry API for the insert-or-update pattern, which is the most common map operation. Use get with Option for the safe lookup. Derive Hash and Eq for the struct keys. And use BTreeMap when the iteration order matters or when the key does not implement Hash.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!