5/5
## Understanding and Using HashMaps in Rust HashMaps are a fundamental and versatile collection type in Rust, designed for storing data as key-value pairs. Similar to vectors, they provide a way to manage collections of data, but with the added advantage of quick lookups based on a unique key. This lesson will guide you through the essentials of working with `HashMap` in Rust, from initialization to common operations like inserting, retrieving, and updating data. ## Importing HashMap Before you can use `HashMap` in your Rust code, you need to bring it into scope. `HashMap` resides in the standard library's `collections` module. You can import it using the `use` keyword: ```rust use std::collections::HashMap; ``` This line makes the `HashMap` type available for use in your current module. ## Initializing a HashMap To start working with a `HashMap`, you first need to create an instance of it. A new, empty `HashMap` is typically created using the `HashMap::new()` associated function. When you declare a `HashMap`, you must specify the data types for both its keys and its values. If you plan to add or modify entries after creation, the `HashMap` variable must be declared as mutable using the `mut` keyword. Let's consider an example where we want to store team names (as `String`s) and their corresponding scores (as `u32` integers): ```rust // Inside fn main() { let mut scores: HashMap<String, u32> = HashMap::new(); // } ``` In this declaration: * `let mut scores`: Declares a mutable variable named `scores`. * `HashMap<String, u32>`: Specifies that this `HashMap` will store keys of type `String` and values of type `u32`. * `HashMap::new()`: Calls the function to create a new, empty hash map. ## Inserting Key-Value Pairs Once your `HashMap` is initialized, you can add data to it using the `insert` method. This method takes two arguments: the key and the value you want to associate with that key. If your keys are of type `String`, and you're using string literals (which are of type `&str`), you'll need to convert them into `String` objects. A common way to do this is by calling the `.to_string()` method. Continuing with our `scores` example: ```rust // Continuing inside fn main() { scores.insert("red".to_string(), 100); scores.insert("blue".to_string(), 200); // } ``` These lines add two entries to our `scores` `HashMap`: * The key `"red"` is associated with the value `100`. * The key `"blue"` is associated with the value `200`. ## Displaying HashMap Contents To inspect the contents of your `HashMap`, you can use the `println!` macro. For complex types like `HashMap`, you'll often use the debug formatter, specified by `{:?}`. For a more readable, "pretty-printed" output, you can use `:#?`. ```rust // Continuing inside fn main() { println!("{:#?}", scores); // } ``` Executing this code will print the key-value pairs stored in the `scores` `HashMap`. The output might look something like this: ``` { "red": 100, "blue": 200, } ``` It's important to note that `HashMap` does not guarantee any specific order for its elements. The order in which items are printed might vary. ## Retrieving Values from a HashMap To access a value associated with a particular key, you use the `get` method. This method takes a reference to the key as its argument. If your `HashMap` uses `String` keys, you can conveniently pass a string literal (an `&str`), as Rust can automatically borrow a `String` as an `&str`. The `get` method doesn't return the value directly. Instead, it returns an `Option<&V>`, where `V` is the type of the values stored in the `HashMap`. This `Option` type is crucial for handling cases where a key might not exist: * If the key is found, `get` returns `Some(&value)`, where `&value` is a reference to the value in the `HashMap`. * If the key is not found, `get` returns `None`. Let's see this in action: ```rust // Continuing inside fn main() { // Get score for "red" team let score: Option<&u32> = scores.get("red"); println!("Red score: {:?}", score); // Output: Red score: Some(100) // Try to get score for a non-existent "green" team let score: Option<&u32> = scores.get("green"); println!("Green score: {:?}", score); // Output: Green score: None // } ``` This demonstrates how `get` safely handles both successful lookups and attempts to retrieve non-existent keys. ## Updating Values in a HashMap Rust provides a powerful and idiomatic way to update values in a `HashMap` using the `entry` method combined with `or_insert`. This pattern is particularly useful when you want to insert a default value if a key doesn't exist, or modify an existing value if it does. Here's how these methods work together: * **`entry(key)`**: This method takes a key as an argument (typically an owned key, like a `String`). It returns an `Entry` enum. This `Entry` represents a view into a specific location in the map, which could either be vacant (the key isn't present) or occupied (the key is present). * **`or_insert(default_value)`**: This method is called on the `Entry` returned by `entry(key)`. * If the `Entry` is vacant (meaning the key did not exist in the `HashMap`), `or_insert` will insert the `default_value` into the `HashMap` at that key. It then returns a mutable reference (`&mut V`) to this newly inserted value. * If the `Entry` is occupied (meaning the key already existed), `or_insert` will *not* use the `default_value`. Instead, it simply returns a mutable reference (`&mut V`) to the existing value. Once you have this mutable reference (`&mut V`), you can modify the value directly within the `HashMap` by dereferencing the reference using the `*` operator. **Example 1: Inserting a new team or updating if it exists** Let's add a "black" team. If it doesn't exist, we'll initialize its score to 0 and then add 100 points. ```rust // Continuing inside fn main() { // Get a mutable reference to the score for "black", inserting 0 if it doesn't exist. let score: &mut u32 = scores.entry("black".to_string()).or_insert(0); // At this point: // - If "black" was not in `scores`, it's now inserted with the value 0. // - `score` is a mutable reference to this 0. // - If "black" was already in `scores`, `score` would be a mutable reference to its existing value. // Increment the score *score += 100; // Dereference `score` to modify the value in the HashMap // Verify the update let black_score = scores.get("black"); println!("Black score: {:?}", black_score); // Output: Black score: Some(100) // } ``` In this scenario, "black" was not initially in the `scores` map. `or_insert(0)` added it with a score of 0. The subsequent `*score += 100;` then updated this score to 100. **Example 2: Updating an existing team's score** Now, let's update the score for the "blue" team, which already exists with a score of 200. We'll add another 100 points. ```rust // Continuing inside fn main() { // The "blue" team already exists with a score of 200. let score: &mut u32 = scores.entry("blue".to_string()).or_insert(0); // Because "blue" exists, `or_insert(0)` does not insert 0. // `score` is now a mutable reference to the existing score of 200 for "blue". *score += 100; // The score of "blue" (initially 200) is incremented by 100. // Verify the update let blue_score = scores.get("blue"); println!("Blue score: {:?}", blue_score); // Output: Blue score: Some(300) // } ``` Here, because "blue" was already in the map, `or_insert(0)` did not change its value. The `score` variable received a mutable reference to the existing value (200), which was then incremented to 300. ## Key Concepts and Best Practices When working with `HashMaps` in Rust, keep these important concepts in mind: * **Mutability**: To insert new key-value pairs or modify existing values, your `HashMap` instance must be declared as mutable (`let mut scores ...`). * **Ownership and Borrowing**: * `HashMap` takes ownership of its keys and values if they are owning types (like `String`). This is why, when inserting, string literals (`&str`) are often converted to `String` using `.to_string()`. * The `get()` method efficiently borrows its key. You can pass an `&str` to `get()` even if the keys are `String`s. * The `entry()` method typically expects an owned key (e.g., `String`). * **The `Option` Type**: The `get()` method returns an `Option`. This is a core Rust feature for handling the potential absence of a value gracefully, preventing unexpected program crashes (panics) that might occur if you tried to access a non-existent key directly. * **Mutable References and Dereferencing**: The `entry(...).or_insert(...)` pattern yields a mutable reference (`&mut V`) to the value in the `HashMap`. To change the actual value stored in the map through this reference, you must dereference it using the asterisk (`*`) operator (e.g., `*score = new_value;` or `*score += amount;`). The example of storing and updating team scores demonstrates a common and practical use case for `HashMaps`. Their ability to associate unique keys with values makes them invaluable for a wide range of data management tasks. By understanding these operations and concepts, you can effectively leverage `HashMap` in your Rust projects.
HashMaps are a fundamental and versatile collection type in Rust, designed for storing data as key-value pairs. Similar to vectors, they provide a way to manage collections of data, but with the added advantage of quick lookups based on a unique key. This lesson will guide you through the essentials of working with HashMap
in Rust, from initialization to common operations like inserting, retrieving, and updating data.
Before you can use HashMap
in your Rust code, you need to bring it into scope. HashMap
resides in the standard library's collections
module. You can import it using the use
keyword:
This line makes the HashMap
type available for use in your current module.
To start working with a HashMap
, you first need to create an instance of it. A new, empty HashMap
is typically created using the HashMap::new()
associated function.
When you declare a HashMap
, you must specify the data types for both its keys and its values. If you plan to add or modify entries after creation, the HashMap
variable must be declared as mutable using the mut
keyword.
Let's consider an example where we want to store team names (as String
s) and their corresponding scores (as u32
integers):
In this declaration:
let mut scores
: Declares a mutable variable named scores
.
HashMap<String, u32>
: Specifies that this HashMap
will store keys of type String
and values of type u32
.
HashMap::new()
: Calls the function to create a new, empty hash map.
Once your HashMap
is initialized, you can add data to it using the insert
method. This method takes two arguments: the key and the value you want to associate with that key.
If your keys are of type String
, and you're using string literals (which are of type &str
), you'll need to convert them into String
objects. A common way to do this is by calling the .to_string()
method.
Continuing with our scores
example:
These lines add two entries to our scores
HashMap
:
The key "red"
is associated with the value 100
.
The key "blue"
is associated with the value 200
.
To inspect the contents of your HashMap
, you can use the println!
macro. For complex types like HashMap
, you'll often use the debug formatter, specified by {:?}
. For a more readable, "pretty-printed" output, you can use :#?
.
Executing this code will print the key-value pairs stored in the scores
HashMap
. The output might look something like this:
It's important to note that HashMap
does not guarantee any specific order for its elements. The order in which items are printed might vary.
To access a value associated with a particular key, you use the get
method. This method takes a reference to the key as its argument. If your HashMap
uses String
keys, you can conveniently pass a string literal (an &str
), as Rust can automatically borrow a String
as an &str
.
The get
method doesn't return the value directly. Instead, it returns an Option<&V>
, where V
is the type of the values stored in the HashMap
. This Option
type is crucial for handling cases where a key might not exist:
If the key is found, get
returns Some(&value)
, where &value
is a reference to the value in the HashMap
.
If the key is not found, get
returns None
.
Let's see this in action:
This demonstrates how get
safely handles both successful lookups and attempts to retrieve non-existent keys.
Rust provides a powerful and idiomatic way to update values in a HashMap
using the entry
method combined with or_insert
. This pattern is particularly useful when you want to insert a default value if a key doesn't exist, or modify an existing value if it does.
Here's how these methods work together:
entry(key)
: This method takes a key as an argument (typically an owned key, like a String
). It returns an Entry
enum. This Entry
represents a view into a specific location in the map, which could either be vacant (the key isn't present) or occupied (the key is present).
or_insert(default_value)
: This method is called on the Entry
returned by entry(key)
.
If the Entry
is vacant (meaning the key did not exist in the HashMap
), or_insert
will insert the default_value
into the HashMap
at that key. It then returns a mutable reference (&mut V
) to this newly inserted value.
If the Entry
is occupied (meaning the key already existed), or_insert
will not use the default_value
. Instead, it simply returns a mutable reference (&mut V
) to the existing value.
Once you have this mutable reference (&mut V
), you can modify the value directly within the HashMap
by dereferencing the reference using the *
operator.
Example 1: Inserting a new team or updating if it exists
Let's add a "black" team. If it doesn't exist, we'll initialize its score to 0 and then add 100 points.
In this scenario, "black" was not initially in the scores
map. or_insert(0)
added it with a score of 0. The subsequent *score += 100;
then updated this score to 100.
Example 2: Updating an existing team's score
Now, let's update the score for the "blue" team, which already exists with a score of 200. We'll add another 100 points.
Here, because "blue" was already in the map, or_insert(0)
did not change its value. The score
variable received a mutable reference to the existing value (200), which was then incremented to 300.
When working with HashMaps
in Rust, keep these important concepts in mind:
Mutability: To insert new key-value pairs or modify existing values, your HashMap
instance must be declared as mutable (let mut scores ...
).
Ownership and Borrowing:
HashMap
takes ownership of its keys and values if they are owning types (like String
). This is why, when inserting, string literals (&str
) are often converted to String
using .to_string()
.
The get()
method efficiently borrows its key. You can pass an &str
to get()
even if the keys are String
s.
The entry()
method typically expects an owned key (e.g., String
).
The Option
Type: The get()
method returns an Option
. This is a core Rust feature for handling the potential absence of a value gracefully, preventing unexpected program crashes (panics) that might occur if you tried to access a non-existent key directly.
Mutable References and Dereferencing: The entry(...).or_insert(...)
pattern yields a mutable reference (&mut V
) to the value in the HashMap
. To change the actual value stored in the map through this reference, you must dereference it using the asterisk (*
) operator (e.g., *score = new_value;
or *score += amount;
).
The example of storing and updating team scores demonstrates a common and practical use case for HashMaps
. Their ability to associate unique keys with values makes them invaluable for a wide range of data management tasks. By understanding these operations and concepts, you can effectively leverage HashMap
in your Rust projects.
A versatile introduction to Rust's `HashMap` - Learn to create and manage key-value pairs, from initialization and data insertion to value retrieval. Discover efficient update techniques, including the `entry().or_insert()` method for adding or modifying map entries.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025
Rust Developer
Rust Programming BasicsDuration: 6min
Duration: 18min
Duration: 47min
Duration: 15min
Duration: 19min
Duration: 8min
Duration: 12min
Duration: 46min
Duration: 14min
Course Overview
About the course
Introduction to the Rust programming language
Rust variables and functions
Scalar types, arrays, strings, enum, structs, vectors, and hash maps in Rust
Rust control flows: If / else, if let and let else, loop, match
Rust ownership, including borrow and references
Rust error handling
Rust Modules
Rust Traits
Last updated on July 11, 2025