DEV Community

Cover image for [Rust Guide] 8.5. HashMap Pt.1 - Defining, Creating, Merging, and Accessing HashMaps
SomeB1oody
SomeB1oody

Posted on

[Rust Guide] 8.5. HashMap Pt.1 - Defining, Creating, Merging, and Accessing HashMaps

8.5.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.5.1. What Is a HashMap?

HashMap is written as HashMap<K, V>, where K stands for key and V stands for value. A HashMap stores data as key-value pairs, with one key corresponding to one value. Many languages support this kind of collection data structure, but they may call it something else—for example, the same concept in C# is called a dictionary.

The internal implementation of HashMap uses a hash function, which determines how keys and values are stored in memory.

In a Vector, we use indices to access data. But sometimes you want to look up data by key, and the key can be any data type, instead of by index, or you may not know which index the data is at. In that case, you can use a HashMap.

Note that HashMap is homogeneous, which means that all keys in one HashMap must be the same type, and all values must be the same type.

8.5.2. Creating a HashMap

  • Because HashMap is not used as often, Rust does not include it in the prelude. Before using it, you need to import HashMap by writing use std::collections::HashMap; at the top of the file.
  • To create an empty HashMap, use the HashMap::new() function.
  • To add data, use the insert() method.

Example:

use std::collections::HashMap;  

fn main() {  
    let mut scores: HashMap<String, i32> = HashMap::new();  
}
Enter fullscreen mode Exit fullscreen mode

Here a variable named scores is created to store a HashMap. Because Rust is a strongly typed language, it must know what data types you are storing in the HashMap. Since there is no surrounding context for the compiler to infer from, you must explicitly declare the key and value types when you declare the HashMap. In this code, the keys of scores are set to String, and the values are set to i32.

Of course, if you later add data to this HashMap, Rust will infer the key and value types from the inserted data. Data is added with the insert() method. Example:

use std::collections::HashMap;  

fn main() {  
    let mut scores = HashMap::new();  
    scores.insert(String::from("dev1ce"), 0);
}
Enter fullscreen mode Exit fullscreen mode

Because a key-value pair is inserted into scores on line 5, and the key String::from("dev1ce") is of type String while the value 0 is of type i32 (Rust’s default integer type is i32), the compiler will infer that scores is a HashMap<String, i32>, so there is no need to explicitly declare the type on the fourth line.

8.5.3. Combining Two Vectors into One HashMap

On a Vector whose element type is a tuple, you can use the collect method to build a HashMap. Put another way, if you have two Vectors and all of the values in them have a one-to-one correspondence, you can use collect to put the data from one Vector into the keys and the data from the other into the values of a HashMap. Example:

use std::collections::HashMap;  

fn main() {  
    let player = vec![String::from("dev1ce"), String::from("Zywoo")];  
    let initial_scores = vec![0, 100];  
    let scores: HashMap<_, _> = player.iter().zip(initial_scores.iter()).collect();  
}
Enter fullscreen mode Exit fullscreen mode
  • The player Vector stores player names, and its elements are of type String.
  • The initial_scores Vector stores the score corresponding to each player.
  • player.iter() and initial_scores.iter() are iterators over the two Vectors. Using .zip() creates a sequence of tuples, and player.iter().zip(initial_scores.iter()) creates tuples with elements from player first and elements from initial_scores second. If you want to swap the order of the elements, you can simply swap the two iterators in the code. Then .collect() is used to convert the tuples into a HashMap.
  • One last thing to note is that .collect() supports conversion into many data structures. If you do not explicitly declare its type when writing the code, the program will fail. Here the type is specified as HashMap<_, _>. The two data types inside <> can be inferred by the compiler from the code, that is, from the two Vector types, so you can use _ as a placeholder and let it infer the types automatically.

8.5.4. HashMap and Ownership

For data types that implement the Copy trait, such as i32 and most simple data types, the value is copied into the HashMap, and the original variable remains usable. For types that do not implement Copy, such as String, ownership is transferred to the HashMap.

If you insert references into a HashMap, the value itself is not moved. During the lifetime of the HashMap, the referenced values must remain valid.

8.5.5. Accessing Values in a HashMap

You can access values with the get method. The get method takes a HashMap key as its argument, and it returns an Option<&V> enum. Example:

use std::collections::HashMap;  

fn main() {  
    let mut scores = HashMap::new();  
    scores.insert(String::from("dev1ce"), 0);  
    scores.insert(String::from("Zywoo"), 100);

    let player_name = String::from("dev1ce");  
    let score = scores.get(&player_name);  
    match score {  
        Some(score) => println!("{}", score),  
        None => println!("Player not found"),  
    };  
}
Enter fullscreen mode Exit fullscreen mode
  • First, an empty HashMap called scores is created, and then two key-value pairs, ("dev1ce", 0) and ("Zywoo", 100), are inserted using insert. The key type is String, and the value type is i32.
  • Then a String variable named player_name is declared with the value "dev1ce".
  • Next, the get method on the HashMap is used to look up the value corresponding to the player_name key in scores (& means reference). But because get returns an Option enum, the Option value is first assigned to score and then unwrapped later.
  • Finally, a match expression is used to handle score. If the corresponding value is found, the score enum is the Some variant, and the value associated with Some is bound to score and then printed. If nothing is found, the score enum is the None variant, and "Player not found" is printed.

Output:

0
Enter fullscreen mode Exit fullscreen mode

8.5.6. Iterating Over a HashMap

You usually iterate over a HashMap with a for loop. Example:

use std::collections::HashMap;  

fn main() {  
    let mut scores = HashMap::new();  
    scores.insert(String::from("dev1ce"), 0);  
    scores.insert(String::from("Zywoo"), 100);  
    for (k, v) in &scores {  
        println!("{}: {}", k, v);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

This for loop uses a reference to the HashMap, namely &scores, because after iterating you usually still want to keep using the HashMap. Using a reference means you do not lose ownership. The (k, v) on the left is pattern matching: the first value is the key, which is assigned to k, and the second is the value, which is assigned to v.

Output:

Zywoo: 100
dev1ce: 0
Enter fullscreen mode Exit fullscreen mode

Top comments (0)