🎓 A1Learner
🔍

Rust Entry API In Depth

The entry API is a HashMap tool that finds a key's slot once and lets the caller decide what belongs there. The Entry enum handles the cases where found and missing need different work.

or_insert_with: the default is built only when needed

or_insert(value) builds its value before the call, needed or not. or_insert_with takes a closure instead — watch the counter stay at 1:

use std::collections::HashMap;

let mut map: HashMap<&str, String> = HashMap::new();
let mut calls = 0;
map.entry("ada").or_insert_with(|| { calls += 1; String::from("first") });
map.entry("ada").or_insert_with(|| { calls += 1; String::from("second") });
println!("value = {} calls = {}", map["ada"], calls);

or_insert_with_key: build the default from the key

Sometimes the default is a fact about the key itself. The closure receives the key, so nothing is cloned:

use std::collections::HashMap;

let mut lengths: HashMap<&str, usize> = HashMap::new();
for word in ["rust", "cargo", "rust"] {
    lengths.entry(word).or_insert_with_key(|k| k.len());
}
println!("rust = {} cargo = {}", lengths["rust"], lengths["cargo"]);

Match the Entry when both cases need real work

The one-line chains cover most jobs. When found and missing each need their own steps, match on the Entry enum itself:

use std::collections::HashMap;
use std::collections::hash_map::Entry;

let mut visits: HashMap<&str, u32> = HashMap::new();
for page in ["home", "about", "home"] {
    match visits.entry(page) {
        Entry::Occupied(mut e) => *e.get_mut() += 1,
        Entry::Vacant(e) => { e.insert(1); }
    }
}
println!("home = {} about = {}", visits["home"], visits["about"]);

Keep the best, not the latest

and_modify runs only when the key exists. So pairing it with max keeps the best score. It does not keep the newest one:

use std::collections::HashMap;

let mut best: HashMap<&str, i32> = HashMap::new();
for (name, score) in [("ada", 30), ("ada", 50), ("ada", 20)] {
    best.entry(name)
        .and_modify(|s| *s = (*s).max(score))
        .or_insert(score);
}
println!("ada best = {}", best["ada"]);

Example

use std::collections::HashMap;

fn main() {
    let games = [("ada", 30), ("bo", 25), ("ada", 50), ("cy", 40), ("bo", 25)];
    let mut best: HashMap<&str, i32> = HashMap::new();
    for (name, score) in games {
        best.entry(name)
            .and_modify(|s| *s = (*s).max(score))
            .or_insert(score);
    }
    let mut lengths: HashMap<&str, usize> = HashMap::new();
    for (name, _) in games {
        lengths.entry(name).or_insert_with_key(|k| k.len());
    }
    let mut names: Vec<&str> = best.keys().copied().collect();
    names.sort();
    for name in names {
        println!("{} best = {} name chars = {}", name, best[name], lengths[name]);
    }
}

Key points

  • entry(key) does one lookup: it finds the slot once, then you decide what belongs there — no get-then-insert double search
  • Entry work starts with one lookup: the slot is found once, then filled, read, changed, or left alone.
  • or_insert_with and or_insert_with_key are lazy: their closures run only when the key is missing
  • or_insert_with_key passes the closure a reference to the key.
  • That key was just moved into entry.
  • The key never needs cloning.
  • or_insert_with_key works in Rust since 1.50.
  • On the occupied side, OccupiedEntry offers four tools.
  • get reads the value.
  • get_mut changes the value.
  • insert replaces the value and hands back the old value.
  • remove_entry takes the whole pair out.

In this example, five game results are folded into two maps. The first loop keeps each player's best score. entry finds the player. and_modify raises the stored score only when the new one beats it. or_insert covers a first game. Ada's 30 is replaced by 50, and her later 20 changes nothing. The second loop builds a side table of name lengths. It uses or_insert_with_key. The default is computed from the key itself. So no name is ever cloned. The report sorts the names first. A HashMap promises no order. Then it prints best score and name length side by side.

Note

HashMap also has a nightly-only raw entry API: raw_entry() and raw_entry_mut(), behind the feature hash_raw_entry.

The raw entry API searches by a hash computed by the caller.

It can postpone building an owned key until an insert is certain.

Library authors use the raw entry API for tricks like hash memoisation.

Everyday code should stay with entry.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does the closure given to or_insert_with_key receive?