Lesson 5
HashMaps in Rust
Introduction to HashMaps in Rust

Hello! Today, we will focus on another powerful and versatile data structure in Rust's std::collections module — HashMaps. HashMaps are invaluable when you need to establish a mapping between a set of keys and a corresponding set of values.

HashMaps store key-value pairs, making it easy to quickly look up values based on their associated keys. This concept is similar to dictionaries in other programming languages like Python. Let's dive in and get familiar with HashMaps!

Creating a HashMap

In Rust, creating a HashMap involves using the HashMap struct from the std::collections module. When creating a new Hashmap, add the data type of the keys and data type of the values inside <>. You can also create an empty HashMap without specifying types, and Rust will infer the types based on how you insert an element into the HashMap for the first time.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 // Create a new HashMap with explicit types 5 let mut hashmap: HashMap<&str, i32> = HashMap::new(); 6 7 // Create a new HashMap with inferred types 8 let mut hashmap_inferred = HashMap::new(); 9}
  • We first import the HashMap struct from the std::collections module.
  • We then create a mutable HashMap named hashmap, which can store &str keys and i32 values.
  • We create a HashMap named hashmap_inferred that will infer the data types when an element is added.
Adding and Accessing Elements

Once you have a HashMap, you can add elements using the insert method. To access a value from a Hashmap use .get followed by the key name. The .get method only accepts a reference.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 let mut hashmap: HashMap<&str, i32> = HashMap::new(); 5 6 // Add elements 7 hashmap.insert("one", 1); 8 hashmap.insert("two", 2); 9 hashmap.insert("three", 3); 10 11 // Access a value 12 let value = hashmap.get("two"); 13 println!("Value under 'two': {:?}", value); // Prints: Value under 'two': Some(2) 14 15 println!("{:?}", hashmap); // Prints: {"one": 1, "two": 2, "three": 3} 16}
  • The insert method adds key-value pairs to the HashMap.
  • The get method accesses the value associated with a key and returns an Option type.
Modifying and Removing Elements

HashMaps provide methods to modify values and remove elements by their keys. To remove an element, use .remove by passing in a variable reference. To modify the value stored in a key, you can reinsert the key with its new value or use .get_mut. .get_mut accepts a reference to the key.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 let mut hashmap: HashMap<&str, i32> = HashMap::new(); 5 hashmap.insert("one", 5); 6 hashmap.insert("two", 2); 7 hashmap.insert("three", 4); 8 9 // Remove an element 10 hashmap.remove("two"); 11 12 // Reinserting an element 13 hashmap.insert("one", 1); 14 15 // Modify an element 16 if let Some(entry) = hashmap.get_mut("three") { 17 *entry = 3; 18 } 19 20 println!("Updated HashMap: {:?}", hashmap); // Prints: Updated HashMap: {"one": 1, "three": 3} 21}
  • The remove method deletes the key-value pair from the HashMap.
  • Re-inserting the key "one" with the value 1, overrides the original value of the key.
  • The get_mut method obtains a mutable reference to the value associated with the key, which allows us to modify it.
Checking for Keys

Checking for the existence of keys in a HashMap can be easily done using the contains_key method. The value passed to contains_key must be a reference.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 let mut squares: HashMap<i32, i32> = HashMap::new(); 5 squares.insert(1, 1); 6 squares.insert(2, 4); 7 8 // Check if a key exists 9 let has_one = squares.contains_key(&1); // Passing a reference 10 let has_three = squares.contains_key(&3); // Passing a reference 11 println!("HashMap contains key 1: {}", has_one); // Prints: HashMap contains key 1: true 12 println!("HashMap contains key 3: {}", has_three); // Prints: HashMap contains key 3: false 13}
  • The contains_key method checks whether a specified key exists in the HashMap.
Understanding Ownership in HashMaps

As with other data structures in Rust, managing ownership and borrowing is crucial when working with HashMaps. Elements added to a HashMap must adhere to Rust's ownership rules.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 // Create an empty HashMap 5 let s = String::from("Hello"); 6 let mut map = HashMap::new(); 7 map.insert("key1", s); // Transfers ownership of "Hello" 8 map.insert("key2", String::from("World")); 9 println!("{}", s); // Causes an error 10}
  • We create a String called s.
  • We create a HashMap map, and insert s with a key and another String element with a different key.
  • Adding s to the HashMap transfers ownership of "Hello" from s to map.
HashMaps as Function Parameters

HashMaps can be passed to functions as references or by value. Understanding how to pass HashMaps to functions allows for more modular and reusable code. Similar to vectors and Hashsets, a HashMap is never copy type, even if the data held in the HashMap is copy type.

Rust
1use std::collections::HashMap; 2 3fn main() { 4 let mut hashmap = HashMap::new(); 5 hashmap.insert("key1", 10); 6 hashmap.insert("key2", 20); 7 8 display_hashmap_reference(&hashmap); 9 println!("After display_hashmap_reference: {:?}", hashmap); // Prints: After display_hashmap_reference: {"key1": 10, "key2": 20} 10 11 display_hashmap_copy(hashmap); // Ownership moved to display_hashmap_copy 12 // println!("After display_hashmap_copy: {:?}", hashmap); // Causes error 13} 14 15fn display_hashmap_reference(map: &HashMap<&str, i32>) { 16 println!("In display_hashmap_reference: {:?}", map); // Prints: In display_hashmap_reference: {"key1": 10, "key2": 20} 17} 18 19fn display_hashmap_copy(map: HashMap<&str, i32>) { 20 println!("In display_hashmap_copy: {:?}", map); // Prints: In display_hashmap_copy: {"key1": 10, "key2": 20} 21}
  • display_hashmap_reference takes a reference to a HashMap, so it doesn't take ownership, allowing the HashMap to remain available after the function call.
  • display_hashmap_copy takes a HashMap by value. Even though the elements are a copyable data type (i32), a HashMap is not copy type, thus ownership is transferred.
Summary and Next Steps

Great job! You've learned about creating and managing HashMaps in Rust, including adding, accessing, modifying, and removing elements. You've also explored how to check for the presence of keys, understood ownership in HashMaps, and learned how to pass HashMaps to functions effectively.

HashMaps are an indispensable tool for organizing and managing key-value pairs. Applying these concepts will significantly enhance your ability to write efficient and robust Rust programs.

Now that you have a solid understanding of HashMaps, it's time to try some hands-on practice to reinforce your learning. Dive into the exercises ahead and start experimenting with HashMaps. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.