Skip to content
Select theme

How to handle missing keys with the entry API

Short answer: map.entry(key) hands back an Entry, and or_insert() puts a default value in when the key is missing — either way you get a &mut V back, so one call does what a get-then-insert pair does with two lookups.

From a fresh project (cargo new entry-demo — the name is mine, and nothing below prints it), replace the contents of src/main.rs with each block and run it with cargo run.

Adding five points to a player means one of two things depending on whether that player is already in the map, and the longhand form says both out loud:

15
5

“Ada” cost two lookups: get_mut found her, so no insert ran. “Grace” cost two as well, the second one the insert. The entry API merges the two branches into one call.

10
5

The standard library describes or_insert as “ensur[ing] a value is in the entry by inserting the default if empty”. The 0 is built on every call, present or not — free for an integer, wasteful for anything expensive. or_insert_with takes a closure instead and only calls it on a miss; or_default inserts V::default():

1
15
5
2

The 1 is the proof: “Ada” was already there, so her default was never built. “Grace” was a miss, so hers was.

get takes any borrowed form of the key — map.get("Ada") works on a HashMap<String, i32> — but entry takes the key by value, so the borrowed form is a type error:

error[E0308]: mismatched types
--> src/main.rs:5:19
|
5 | *scores.entry("Ada").or_insert(0) += 5;
| ----- ^^^^^ expected `String`, found `&str`
| |
| arguments to this method are incorrect
|
help: the return type of this call is `&'static str` due to the type of the argument passed
--> src/main.rs:5:6
|
5 | *scores.entry("Ada").or_insert(0) += 5;
| ^^^^^^^^^^^^^-----^
| |
| this argument influences the return type of `entry`
note: method defined here
--> /Users/rango/.rustup/toolchains/1.99.0-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/collections/hash/map.rs:1012:12
|
1012 | pub fn entry(&mut self, key: K) -> Entry<'_, K, V, A> {
| ^^^^^
help: try using a conversion method
|
5 | *scores.entry("Ada".to_string()).or_insert(0) += 5;
| ++++++++++++
For more information about this error, try `rustc --explain E0308`.

The compiler’s own suggestion is the fix: "Ada".to_string(). The cost is an allocation on every call, hit or miss — a counting allocator measured one per call where get_mut costs none. In a hot loop over a String-keyed map, that changes the maths.

When the same key can arrive both ways, and_modify runs a closure over the existing value and chains into or_insert for the miss:

10
5

When neither helper fits, entry returns an enum you can match on: Entry::Occupied and Entry::Vacant, with insert on the vacant side and get, get_mut and key on the occupied one. BTreeMap has the same entry API, owned-key rule included.

Next in this chapter: the difference between HashMap and BTreeMap.