HashMap in Rust: insert, get and the entry API
How to use HashMap in Rust: insert, get, contains_key, remove, counting with entry().or_insert(), and printing keys in a stable order.
A HashMap<K, V> stores values by key, with average O(1) lookup. It isn't in the prelude, so import it: use std::collections::HashMap;.
use std::collections::HashMap;
fn main() {
let mut stock: HashMap<String, u32> = HashMap::new();
stock.insert(String::from("apples"), 12);
stock.insert(String::from("pears"), 4);
println!("{:?}", stock.get("apples"));
println!("{:?}", stock.get("kiwis"));
println!("{}", stock.contains_key("pears"));
let old = stock.insert(String::from("pears"), 9);
println!("replaced {old:?}, now {}", stock["pears"]);
}
Some(12)
None
true
replaced Some(4), now 9
insertreturns the old value as anOption(Noneif the key was new).getreturnsOption<&V>. A map withStringkeys can be searched with a&str.map[&key]panics if the key is missing. Prefergetunless absence is a bug.remove(&key)returns the removed value as anOption.
The entry API
The most common map job is "update the value for this key, or start it with a default". The entry API does it with one lookup:
*counts.entry(word).or_insert(0) += 1;
entry(key) finds the slot for the key. or_insert(default) inserts the default if it's empty and returns a &mut V either way; *... += 1 updates it. Variants:
| Call | Use |
|---|---|
.or_insert(v) |
insert v if missing |
.or_default() |
insert 0, "", empty Vec... |
.or_insert_with(|| ...) |
build the default only when needed |
.and_modify(|v| ...).or_insert(x) |
change an existing value, or insert x |
Grouping is the other classic: groups.entry(dept).or_default().push(name) builds a HashMap<Dept, Vec<Name>>.
Iteration order is random
A HashMap iterates in an unspecified order that changes between runs (Rust seeds its hash function randomly to resist denial-of-service attacks). Never rely on it for output. When order matters:
- collect the entries into a
Vecand sort it, or - use a
BTreeMap, which keeps keys sorted (O(log n) operations, same API).
Keys
Keys must implement Eq and Hash. Strings, integers, char, bool, tuples of those, and your own types with #[derive(PartialEq, Eq, Hash)] all work. f64 doesn't (NaN isn't equal to itself); store prices in cents as integers instead.
Common mistakes
- Printing a map directly in output that must be stable (tests, reports): sort first.
gettheninsert(two lookups, and a borrow conflict if you hold thegetreference): useentry.- Moving keys in:
map.insert(name, 1)moves aStringname; insertname.clone()if you still need it, or use&strkeys when the strings outlive the map. - Holding a reference from
getwhile inserting is E0502: copy the value out first.
Example
use std::collections::{BTreeMap, HashMap};
fn main() {
let log = "GET /home GET /about POST /login GET /home GET /home POST /login GET /pricing";
let mut hits: HashMap<&str, u32> = HashMap::new();
for word in log.split_whitespace() {
if word.starts_with('/') {
*hits.entry(word).or_insert(0) += 1;
}
}
// Sort by count (highest first), then by path, for stable output.
let mut pages: Vec<(&str, u32)> = hits.iter().map(|(k, v)| (*k, *v)).collect();
pages.sort_by(|a, b| b.1.cmp(&a.1).then(a.0.cmp(b.0)));
for (page, n) in &pages {
println!("{page:<10} {n}");
}
println!("home {:?}, admin {:?}", hits.get("/home"), hits.get("/admin"));
let mut by_dept: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
for (name, dept) in [("Ada", "eng"), ("Grace", "eng"), ("Linus", "ops"), ("Ferris", "eng")] {
by_dept.entry(dept).or_default().push(name);
}
println!("{by_dept:?}");
let mut stock: BTreeMap<String, i32> = BTreeMap::new();
stock.insert(String::from("apples"), 3);
for fruit in ["apples", "pears"] {
stock.entry(fruit.to_string()).and_modify(|n| *n += 10).or_insert(1);
}
println!("{stock:?}");
}
Output:
/home 3
/login 2
/about 1
/pricing 1
home Some(3), admin None
{"eng": ["Ada", "Grace", "Ferris"], "ops": ["Linus"]}
{"apples": 13, "pears": 1}