| |

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

MethodPurpose
HashMap::new()Empty map
HashMap::with_capacity(n)Pre-allocated
collect()From an iterator of tuples

Access

MethodReturns
get(&key)Option<&V>
get_mut(&key)Option<&mut V>
contains_key(&key)bool
len()Number of entries
is_empty()bool

Modification

MethodEffect
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

MethodYields
&map(&K, &V)
&mut map(&K, &mut V)
map(K, V)
keys()&K
values()&V
values_mut()&mut V

Key Bounds

BoundRequired For
HashHashing the key
EqComparing keys
PartialEqEquality 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

PitfallWhy It HappensFix
Key not foundget returns NoneUse unwrap_or
Key movedinsert takes ownershipClone or pass reference
Order not preservedHashMap is unorderedUse BTreeMap
Hash collisionBad hash functionUse the default hasher
f64 key errorHash not implementedUse a wrapper
Borrow conflictHolding &mut across insertDrop the reference
Entry API misuseor_insert always evaluatedUse 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

ItemValue
TypeHashMap<K, V>
Importstd::collections::HashMap
CreateHashMap::new()
Insertmap.insert(k, v)
Getmap.get(&k) → Option<&V>
Containsmap.contains_key(&k)
Removemap.remove(&k) → Option<V>
Entrymap.entry(k)
Insert if absent.or_insert(v)
Key boundsHash + Eq
Iteration&map, &mut map, map
Ordered alternativeBTreeMap

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 a BTreeMap when the order matters.
  • The get method returns Option<&V>. The key may not be present, and the Option forces the caller to handle the absence. The unwrap_or provides a default.
  • The insert method replaces the old value and returns it. The map has one value per key. The old value is returned as Option<V>.
  • The entry API handles the insert-or-update pattern. The entry(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 Hash and Eq. The primitives, the strings, and the derived structs implement them. The f64 does not, because NaN is 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 BTreeMap is the ordered alternative. It uses the Ord bound instead of Hash and Eq, 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!