Hash Maps and Hash Sets
or_insert_with()
The .or_insert_with(f) and .or_insert_with_key(f) methods on Entry<'a, K, V> lazily construct a fallback value using an FnOnce closure only when the entry is Vacant, avoiding wasted allocations or computations on cache hits.
Signatures
impl<'a, K, V> Entry<'a, K, V> {
pub fn or_insert_with<F: FnOnce() -> V>(self, default: F) -> &'a mut V;
pub fn or_insert_with_key<F: FnOnce(&K) -> V>(self, default: F) -> &'a mut V;
}
| Method | When Closure Runs | Closure Parameter | Typical Use Case |
|---|---|---|---|
e.or_insert(val) |
(Always evaluated eagerly before call) | — | Cheap Copy literals (0, true, None) |
e.or_insert_with(|| ...) |
Only when e is Vacant |
() |
Allocating Vec::with_capacity, String, or computing an expensive value |
e.or_insert_with_key(|k| ...) |
Only when e is Vacant |
&K (shared reference to the entry's key) |
Deriving the initial value from the key itself without cloning the key |
Lazy Initialization Only on Cache Misses
use std::collections::HashMap;
fn expensive_compute(key: &str) -> String {
println!(" [computing value for '{key}'...]");
format!("computed:{key}")
}
fn main() {
let mut cache: HashMap<&str, String> = HashMap::new();
// 1st lookup of "user_1" -> Vacant -> closure runs:
let v1 = cache
.entry("user_1")
.or_insert_with(|| expensive_compute("user_1"));
println!("1st result: {v1}");
// 2nd lookup of "user_1" -> Occupied -> closure does NOT run!
let v2 = cache
.entry("user_1")
.or_insert_with(|| expensive_compute("user_1"));
println!("2nd result: {v2}");
}
[computing value for 'user_1'...]
1st result: computed:user_1
2nd result: computed:user_1
Deriving the Initial Value from the Key (or_insert_with_key)
When a non-Copy key (such as an owned String) is moved into map.entry(key), you can no longer read key inside a regular .or_insert_with(|| ...) closure because key was moved into the Entry. or_insert_with_key(|k| ...) passes &K directly from the VacantEntry to your closure:
use std::collections::HashMap;
fn main() {
let mut word_lengths: HashMap<String, usize> = HashMap::new();
for raw in ["rust", "collections", "rust"] {
let owned_key = String::from(raw);
// `owned_key` is moved into `entry(...)`, and `or_insert_with_key`
// borrows `&String` as `k` to compute `k.len()` only if vacant:
let len = *word_lengths
.entry(owned_key)
.or_insert_with_key(|k| k.len());
println!("{raw:<12} -> len = {len}");
}
}
rust -> len = 4
collections -> len = 11
rust -> len = 4