8.6.0. Chapter Overview
Chapter 8 is mainly about common collections in Rust. Rust provides many collection-like data structures, and these collections can hold many values. However, the collections covered in Chapter 8 are different from arrays and tuples.
The collections in Chapter 8 are stored on the heap rather than on the stack. That also means their size does not need to be known at compile time; at runtime, they can grow or shrink dynamically.
This chapter focuses on three collections: Vector, String, and HashMap (this article).
If you find this helpful, please like, bookmark, and follow. To keep learning along, follow this series.
8.6.1. Updating a HashMap
A variable-sized HashMap means that the number of key-value pairs can change. However, at any given moment, one key can correspond to only one value. When you want to update data in a HashMap, there are several possible cases:
-
The key you want to update already has a corresponding value in the
HashMap:- Replace the existing value with a new value
- Keep the existing value and ignore the new value
- Merge the existing value with the new value, which means modifying the existing value
The key does not exist: add a key-value pair
1. Replacing an Existing Value
If you insert a key-value pair into a HashMap, but the key already exists, the program assigns the new value to that key and overwrites the old one. Example:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
scores.insert(String::from("dev1ce"), 60);
println!("{:?}", scores);
}
Here the same key is assigned a value twice: first 0, then 60. The first value is overwritten by the second, which means the final value corresponding to "dev1ce" is 60.
Output:
{"dev1ce": 60}
2. Insert a Value Only if the Key Has No Existing Value
This is the most common case. In this situation, you first need to check whether the original HashMap already contains the key. If it does not, then insert the new value.
Rust provides the entry method to check whether the original HashMap already contains the key. Its argument is the key, and its return value is an Entry enum, which represents whether the value exists. Example:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
let e = scores.entry(String::from("dev1ce"));
println!("{:?}", e);
}
This is the case where the key already exists. Output:
Entry(OccupiedEntry { key: "dev1ce", value: 0, .. })
In other words, if the key already exists, the entry method returns the OccupiedEntry variant of the Entry enum and associates it with the existing key-value pair.
Now let’s try the case where the key does not exist. Code:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
let e = scores.entry(String::from("Zywoo"));
println!("{:?}", e);
}
Output:
Entry(VacantEntry("Zywoo"))
If the key does not exist, it returns the VacantEntry variant of the Entry enum and associates it with the new key.
Now that we can check whether the original HashMap already contains the key, how do we insert or skip insertion based on whether it exists?
Rust provides the or_insert method, whose argument is the value you want to add. It accepts an Entry enum and uses its two variants to decide whether to insert. If it receives OccupiedEntry (the key already exists), it keeps the existing value and does not insert a new one. If it receives VacantEntry (the key does not exist), it inserts the provided value. Most importantly, it returns a value: a mutable reference to the value for that key. If the key already exists, it returns a mutable reference to the value already in the HashMap; if the key does not exist, it first inserts the key-value pair and then returns a mutable reference to the inserted value. This behavior can be used to build simple counters, as we will see later.
Example:
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("dev1ce"), 0);
scores.entry(String::from("Zywoo")).or_insert(100);
scores.entry(String::from("dev1ce")).or_insert(60);
println!("{:?}", scores);
}
- The first
entrystatement looks up"Zywoo". Since it is not found,VacantEntryis returned, andor_insertreceives it and creates the key-value pair("Zywoo", 100)using the key associated withVacantEntryand the argument100. - The second
entrystatement looks up"dev1ce". Since it is found,OccupiedEntryis returned, andor_insertreceives it and stops the insertion of a new value, so("dev1ce", 0)remains unchanged.
Output:
{"Zywoo": 100, "dev1ce": 0}
If this still feels complicated, you can think of scores.entry(String::from("Zywoo")).or_insert(100); as two lines of code:
let e = scores.entry(String::from("Zywoo"));
e.or_insert(100);
3. Updating Based on Existing Values
Let’s start with an example:
use std::collections::HashMap;
fn main() {
let text = "That's one small step for [a] man, one giant leap for mankind.";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:#?}", map);
}
- First, a string literal containing a sentence is declared and assigned to
text. - Then a
HashMapcalledmapis created. - Next comes the
forloop.text.split_whitespace()splitstextinto an iterator over strings, andforis used to iterate over it. - During iteration, the code checks whether each word appears in the
map. If it does, no new value is inserted. If it does not,0is inserted as the new value for that key. The key thing to understand iscount: because the return value ofor_insertis a mutable reference to the value for that key, each time a word appears, the code dereferences the mutable reference and adds1, which is equivalent to completing one count.
8.6.2. Hash Functions
By default, HashMap uses a cryptographically strong hash function that can resist denial-of-service (DoS) attacks. However, this function is not the fastest hash algorithm available; its advantage is better security. If you think its performance is not good enough, you can also specify a different hasher to switch to another function. A hasher refers to a type that implements the BuildHasher trait.
Top comments (0)