A Rust HashMap is a type of container that lets you store pairs of data — one part is a key, and the other is a value. You use the key to quickly find the value, kind of like looking up a word in a dictionary to find its meaning.
Think of it like:
- Key = word
- Value = definition
So if you had a HashMap with:
"apple" => "a fruit"
"car" => "a vehicle"Code language: Rust (rust)You could ask it: “What’s the value for ‘car’?” and it would give you “a vehicle.”
Unlike a vector (Vec), HashMap doesn’t guarantee the order of its elements, and each key must be unique. If you insert a key that already exists, it will overwrite the old value.
Creating a HashMap #
You can create an empty HashMap using HashMap::new(). For example:
use std::collections::HashMap;
fn main() {
// Create an empty, mutable HashMap to store String keys and i32 values
let mut scores = HashMap::new();
// Insert key-value pairs
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Red"), 50);
// Print the HashMap
println!("{:?}", scores);
}Code language: Rust (rust)Output:
{"Red": 50, "Blue": 10}Code language: Rust (rust)Alternatively, you can create a HashMap by collecting a vector of tuples into a HashMap using the .collect() method. This is especially useful for quickly initializing a map from existing data. For example:
use std::collections::HashMap;
fn main() {
// Two vectors, one for keys and one for values
let teams = vec![String::from("Blue"), String::from("Red")];
let initial_scores = vec![10, 50];
// Use .zip() to pair up the vectors into (key, value) tuples,
// then use .collect() to turn them into a HashMap.
let scores_from_collect: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
// Print the resulting HashMap
println!("{:?}", scores_from_collect);
}Code language: Rust (rust)Output:
{"Blue": 10, "Red": 50}Code language: Rust (rust)Accessing Values #
You can access a value in a HashMap using the .get() method. This method returns an Option<&V>, which is either Some(&value) if the key exists or None if it doesn’t. This prevents potential runtime errors.
To handle the Option safely, you can use a match expression, if let, or methods like .unwrap_or().
You can also iterate over the HashMap to access all the key-value pairs. Iterating over a HashMap gives you references to the keys and values.
use std::collections::HashMap;
fn main() {
let scores = HashMap::from([
(String::from("Blue"), 10),
(String::from("Red"), 50)
]);
// Access a value safely with .get()
let team_name = String::from("Blue");
let score = scores.get(&team_name);
match score {
Some(s) => println!("The score for {} is: {}", team_name, s),
None => println!("The score for {} was not found.", team_name),
}
// Iterate over the HashMap
for (key, value) in &scores {
println!("Team: {}, Score: {}", key, value);
}
}Code language: Rust (rust)Output:
The score for Blue is: 10
Team: Blue, Score: 10
Team: Red, Score: 50Code language: Rust (rust)Updating and Managing Values #
HashMap provides several ways to update values:
Modifying a value in place: The .entry().or_insert() pattern is particularly powerful because it gives you a mutable reference (&mut V), which you can then use to modify the value directly without having to look it up again. This is great for tasks like counting or summing.
Overwriting with .insert(): If a key already exists, insert() will replace the old value with the new one. It returns Some(old_value) if the key was present, otherwise None.
Inserting only if a key is not present: Use the .entry() method, which returns an Entry enum. This allows you to check if a key is present and perform an action based on the result. The .or_insert() method on Entry returns a mutable reference to the value. If the key exists, it returns a mutable reference to the existing value. If not, it inserts the default value and returns a mutable reference to the new value.
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
// Overwriting a value
scores.insert(String::from("Blue"), 25);
println!("Scores after overwriting: {:?}", scores);
// Using .entry().or_insert() to insert only if not present
scores.entry(String::from("Red")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(20); // This won't insert as "Blue" exists
println!("Scores after .entry().or_insert(): {:?}", scores);
// Incrementing a value in place
let mut team_scores = HashMap::new();
let blue_team_score = team_scores.entry(String::from("Blue")).or_insert(0);
*blue_team_score += 1; // You must dereference the mutable reference with `*`
println!("Scores after incrementing: {:?}", team_scores);
}Code language: Rust (rust)Output:
Scores after overwriting: {"Blue": 25}
Scores after .entry().or_insert(): {"Red": 50, "Blue": 25}
Scores after incrementing: {"Blue": 1}Code language: Rust (rust)Ownership and Traits #
Like all data structures in Rust, a HashMap adheres to ownership rules. When you insert a key and value, the HashMap takes ownership of them. This means the key and value cannot be used again after they’ve been moved into the map.
The types used for keys and values in a HashMap must satisfy certain trait requirements:
- The key type
Kmust implement theEqandHashtraits. TheEqtrait allows for equality comparison, and theHashtrait allows the type to be hashed. - The value type
Vmust be a type that can be owned and can be copied or cloned if needed.
Most primitive types (like i32, f64, bool, and char), String, and many other standard library types already implement these traits. This is why you can use String as a key without any extra work. If you want to use a custom struct as a key, you’ll need to derive or implement these traits for it.
use std::collections::HashMap;
fn main() {
let name = String::from("Alice");
let mut map = HashMap::new();
map.insert(name, 25);
// name is moved into map, so we can't use it anymore here
// println!("{}", name); ❌ compile error
}
Code language: Rust (rust)HashMap Example: Word Frequency Counter #
use std::collections::HashMap;
fn main() {
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map); // {"hello": 1, "world": 2, "wonderful": 1}
}
Code language: Rust (rust)Output:
{"hello": 1, "world": 2, "wonderful": 1}Code language: Rust (rust)Summary #
HashMap<K, V>stores unique keys with values.- Access with
.get()→ returnsOption<&V>. - Update with
.insert(),.entry().or_insert(). - Keys and values are moved into the map.
- Useful for counting, caching, lookups, etc.