Question
I want to retrieve a Vec from a Rust HashMap by key and keep a reference to it for later use. If the key does not exist, I want to create and insert an empty Vec for that key, then still use that inserted value through the same variable.
I first tried using match with map.get(key), like this:
use std::collections::HashMap;
let mut map: HashMap<&str, Vec<isize>> = HashMap::new();
let key = "foo";
let values: &Vec<isize> = match map.get(key) {
Some(v) => v,
None => {
let default: Vec<isize> = Vec::new();
map.insert(key, default);
&default
}
};
However, this does not compile because map.get(key) creates an immutable borrow, and then map.insert(...) tries to mutably borrow the same HashMap.
I then changed it to this, which works but performs two lookups:
use std::collections::HashMap;
let mut map: HashMap<&str, Vec<isize>> = HashMap::new();
let key = "foo";
if !map.contains_key(key) {
map.insert(key, Vec::new());
}
let values: &Vec<isize> = match map.get(key) {
Some(v) => v,
None => panic!("this should be impossible")
};
Is there a safe and efficient Rust approach that does this in a single operation, ideally with one lookup?
Short Answer
By the end of this page, you will understand why borrowing rules make the original match map.get(...) approach fail, and how Rust's HashMap::entry API solves the problem cleanly. You will learn how to look up a value, insert a default if missing, and get a reference or mutable reference in one efficient operation.
Concept
Rust prevents you from having an immutable borrow and a mutable borrow of the same value at the same time. In your original code, map.get(key) immutably borrows the HashMap, and inside the None branch you try to call map.insert(...), which needs a mutable borrow. That combination is not allowed.
This matters because Rust uses borrowing rules to prevent bugs like:
- reading stale references
- invalidating references after mutation
- data races in concurrent situations
For this exact pattern, Rust provides the Entry API on HashMap. The idea is simple:
- check whether a key exists
- if it exists, use the existing value
- if it does not exist, insert one
- return access to the value either way
This is both idiomatic and efficient because it avoids doing a separate contains_key and get lookup.
The most common methods are:
entry(key)— access an entry for a keyor_insert(value)— insertvalueif missing, otherwise return the existing oneor_insert_with(|| ...)— compute the default only when needed
Mental Model
Think of a HashMap like a wall of lockers.
get(key)means: "Open the building directory and look at lockerkeywithout changing anything."insert(key, value)means: "Possibly add or change a locker."
Rust does not let you say: "While I am still in read-only inspection mode, also modify the lockers." That is what your original code tries to do.
The entry API is like asking the building manager: "Give me the locker for this key. If it doesn't exist, create it first." You make one request, and the manager handles both cases safely.
Syntax and Examples
Core syntax
use std::collections::HashMap;
let mut map: HashMap<&str, Vec<isize>> = HashMap::new();
let values = map.entry("foo").or_default();
values is a &mut Vec<isize> here. If the key exists, you get a mutable reference to the existing vector. If not, Rust inserts an empty vector and gives you a mutable reference to that.
Example: lookup or insert
use std::collections::HashMap;
fn main() {
let mut map: HashMap<&str, Vec<isize>> = HashMap::new();
let values: &mut Vec<isize> = map.entry("foo").or_default();
values.push();
values.();
(, map);
}
Step by Step Execution
Consider this code:
use std::collections::HashMap;
fn main() {
let mut map: HashMap<&str, Vec<i32>> = HashMap::new();
let key = "foo";
let values = map.entry(key).or_default();
values.push(5);
values.push(8);
println!("{:?}", map);
}
Step by step:
-
mapstarts empty.{} -
map.entry(key)asks the map for the entry at"foo". -
Since
"foo"is not present,or_default()insertsVec::default(), which is an empty vector.
Real World Use Cases
This pattern appears constantly in real Rust programs.
Grouping items by key
use std::collections::HashMap;
let mut groups: HashMap<&str, Vec<i32>> = HashMap::new();
groups.entry("even").or_default().push(2);
groups.entry("even").or_default().push(4);
groups.entry("odd").or_default().push(3);
Useful for:
- categorizing data
- building indexes
- aggregating records
Counting occurrences
use std::collections::HashMap;
let words = ["cat", "dog", "cat"];
let mut counts: HashMap<&, > = HashMap::();
words {
*counts.(word).() += ;
}
Real Codebase Usage
In real projects, developers often use the entry API as the standard way to avoid double lookups and awkward borrow issues.
Common pattern: accumulate values
use std::collections::HashMap;
fn add_tag(map: &mut HashMap<String, Vec<String>>, user: String, tag: String) {
map.entry(user).or_default().push(tag);
}
This is common in:
- request aggregation
- event grouping
- report generation
Common pattern: counters
use std::collections::HashMap;
fn count_event(counts: &mut HashMap<String, usize>, event: String) {
*counts.entry(event).or_insert(0) += 1;
}
Guarding expensive construction
Use or_insert_with when creating the default is expensive:
Common Mistakes
1. Borrowing with get() and then trying to insert()
Broken code:
use std::collections::HashMap;
let mut map: HashMap<&str, Vec<i32>> = HashMap::new();
let key = "foo";
let values = match map.get(key) {
Some(v) => v,
None => {
map.insert(key, Vec::new());
map.get(key).unwrap()
}
};
Why it fails:
get()creates an immutable borrowinsert()needs a mutable borrow- both overlap in the
match
Fix:
let values = map.(key).();
Comparisons
| Approach | Lookups | Inserts if missing | Borrow-friendly | Idiomatic Rust | Best use case |
|---|---|---|---|---|---|
get() + insert() manually | 1 or more | Yes | Often awkward | No | Rare, when logic is very custom |
contains_key() + get() | 2 | Yes | Safe | Not ideal | Simple code, but less efficient |
entry(...).or_insert(...) | 1 | Yes | Yes | Yes |
Cheat Sheet
Quick reference
use std::collections::HashMap;
Insert if missing, then get mutable reference
let value = map.entry(key).or_default();
Insert a specific default
let value = map.entry(key).or_insert(Vec::new());
Insert using lazy construction
let value = map.entry(key).or_insert_with(Vec::new);
Count items
*map.entry(key).or_insert(0) += 1;
Group values
map.(key).().(item);
FAQ
Why does map.get() followed by map.insert() fail in Rust?
Because get() immutably borrows the map, while insert() needs a mutable borrow. Rust does not allow those overlapping borrows.
What is the idiomatic Rust way to get-or-insert in a HashMap?
Use the entry API, usually with or_insert, or_insert_with, or or_default.
Should I use or_default() or or_insert(Vec::new())?
For a Vec, or_default() is usually cleaner and more idiomatic.
Does entry() do only one lookup?
Yes, that is one of its main benefits. It is designed for efficient get-or-insert behavior.
What type does or_default() return?
It returns &mut V, a mutable reference to the value in the map.
Mini Project
Description
Build a small Rust program that groups numbers by a label in a HashMap. This demonstrates the exact get or insert pattern that the entry API is designed for. It is a practical example because grouping values by category is common in data processing, logging, analytics, and report generation.
Goal
Create a program that stores numbers under category names and automatically creates a new Vec for a category the first time it is used.
Requirements
- Create a
HashMapwhere the key is a string slice and the value is aVec<i32>. - Add several numbers into categories such as
"even"and"odd". - Use the
entryAPI so missing categories are created automatically. - Print the final map contents.
- Print the values stored for one chosen category.
Keep learning
Related questions
Accessing Cargo Package Metadata in Rust
Learn how to read Cargo package metadata like version, name, and authors in Rust using compile-time environment macros.
Associated Types vs Generic Type Parameters in Rust: When to Use Each
Learn when to use associated types vs generic parameters in Rust traits, with clear rules, examples, and practical API design advice.
Convert an Integer to a String in Rust
Learn the current Rust way to convert integers to strings, why `to_str()` no longer works, and when to use `to_string()` or `format!`.