Hash Maps and Hash Sets
The Entry API
Calling map.entry(key) on a HashMap<K, V> hashes key once, probes the bucket table once, and returns an Entry<'a, K, V> enum that borrows &'a mut HashMap<K, V> and points directly to either the occupied bucket or the vacant insertion slot.
Types in std::collections::hash_map
pub enum Entry<'a, K: 'a, V: 'a> {
Occupied(OccupiedEntry<'a, K, V>),
Vacant(VacantEntry<'a, K, V>),
}
map.entry(key) (Hashes `key` ONCE and probes table ONCE)
│
├── Key found in bucket `i` ──> Entry::Occupied(OccupiedEntry)
│ ├── .get() / .get_mut() / .into_mut()
│ ├── .insert(new_v) -> old_v
│ └── .remove() / .remove_entry()
│
└── Key absent (slot `i` ready) ──> Entry::Vacant(VacantEntry)
├── .key()
└── .insert(v) -> &'a mut V
Combinator Methods on Entry<'a, K, V>
| Method | Signature | Behavior |
|---|---|---|
e.key() |
fn key(&self) -> &K |
Returns a reference to the entry's key (either in the map or inside VacantEntry) |
e.and_modify(f) |
fn and_modify<F: FnOnce(&mut V)>(self, f: F) -> Self |
Runs f(&mut v) before any potential insertion if the entry is already Occupied |
e.or_insert(v) |
fn or_insert(self, default: V) -> &'a mut V |
Inserts default if Vacant, returning &'a mut V |
e.or_insert_with(f) |
fn or_insert_with<F: FnOnce() -> V>(self, f: F) -> &'a mut V |
Calls f() lazily if Vacant, returning &'a mut V |
e.or_insert_with_key(f) |
fn or_insert_with_key<F: FnOnce(&K) -> V>(self, f: F) -> &'a mut V |
Calls f(e.key()) lazily if Vacant, returning &'a mut V |
e.or_default() |
fn or_default(self) -> &'a mut V (V: Default) |
Inserts V::default() if Vacant, returning &'a mut V |
e.insert_entry(v) |
fn insert_entry(self, value: V) -> OccupiedEntry<'a, K, V> |
Unconditionally sets the entry's value to v and returns an OccupiedEntry |
Using and_modify vs. Matching on Entry
use std::collections::HashMap;
use std::collections::hash_map::Entry;
fn main() {
let mut scores = HashMap::from([("alice", 10_u32)]);
// 1. Combinator style: increment by 5 if occupied, or initialize to 1 if vacant:
for player in ["alice", "bob"] {
scores
.entry(player)
.and_modify(|s| *s += 5)
.or_insert(1);
}
println!("After combinators: alice={}, bob={}", scores["alice"], scores["bob"]);
// 2. Pattern matching directly on `Entry::Occupied` and `Entry::Vacant`:
match scores.entry("alice") {
Entry::Occupied(occ) => {
println!("Removing occupied entry '{}' = {}", occ.key(), occ.remove());
}
Entry::Vacant(vac) => {
vac.insert(100);
}
}
println!("Remaining keys: {:?}", scores.keys().collect::<Vec<_>>());
}
After combinators: alice=15, bob=1
Removing occupied entry 'alice' = 15
Remaining keys: ["bob"]