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!
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.
Rust1use 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 thestd::collections
module. - We then create a mutable
HashMap
namedhashmap
, which can store&str
keys andi32
values. - We create a
HashMap
namedhashmap_inferred
that will infer the data types when an element is added.
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.
Rust1use 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 theHashMap
. - The
get
method accesses the value associated with a key and returns anOption
type.
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.
Rust1use 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 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.
Rust1use 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 theHashMap
.
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.
Rust1use 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
calleds
. - We create a
HashMap
map
, and inserts
with a key and anotherString
element with a different key. - Adding
s
to theHashMap
transfers ownership of "Hello" froms
tomap
.
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.
Rust1use 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 aHashMap
, so it doesn't take ownership, allowing theHashMap
to remain available after the function call.display_hashmap_copy
takes aHashMap
by value. Even though the elements are a copyable data type (i32
), aHashMap
is not copy type, thus ownership is transferred.
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!