Learning Rust 🦀: 18 - Rust Collections: HashMaps, accessing values with keys instead of indices
Tags: learning, rust, programming, beginners
Published: 2023-10-20 14:48:37

I'll continue Rust's collections discusion that started two articles ago with Vectors and Strings as a special kind of Vectors. In this last dedicated article about collections, I'll bring out the __HashMaps__! Let's begin.

>#### ⚠️ Remember!
You can find all the code snippets for this series in its [accompanying repo](https://github.com/FadyGrAb/rust-learning-series)  
If you don't want to install Rust locally, you can play with all the code of this series in the official [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021) that can be found on its official page.

> ⚠️⚠️ The articles in this series are loosely following the contents of "The Rust Programming Language, 2nd Edition" by Steve Klabnik and Carol Nichols in a way that reflects my understanding from a Python developer's perspective.

⭐ I try to publish a new article every week (maybe more if the Rust gods 🙌 are generous 😁) so stay tuned 😉. I'll be posting "new articles updates" on my [LinkedIn](https://www.linkedin.com/in/fady-abdelmassih) and [X (fromarly Twitter)](https://twitter.com/fadyGrAb).

## Table of Contents:
- [What is a Hash Map](#what-is-a-hash-map)
- [Creating a Hash Map](#creating-a-hash-map)
- [Accessing values in a Hash Map](#accessing-values-in-a-hash-map)
- [Updating a Hash Map](#updating-a-hash-map)

### What is a Hash Map:
__Hash Maps__ aren't something native to Rust. Other programming languages has similar structures but with different names, like _Dictionaries_ in Python, _Objects_ in Javascrip, _Hash Tables_ in Powershell (althoug not a programming language per-sey), and many more.
They work more or less the same way as you would expect! You retreive a _Value_ with a _Key_ instead of and _index_ as in __Vectors__.
Rust's __HashMap__ is pretty similar to Python's __Dictionary__ except that in Rust, the __HashMap__ must have the keys of the same  type and the values also must be of the same type but the keys and values can be of similar of different types than eachother.
> Hash Maps is written in Rust as `HashMap<K, V>` where `K` is the keys' type and `V` is the values' type which can be the same.

I'll demonstrate here the basic API of Rust's __Hash Maps__ but as always, the documentations contains a lot of other metods that can be very useful.

### Creating a Hash Map:
Appearently __Hash Maps__ isn't used much in Rust that they aren't loaded in the _prelude_ like __Vectors__ and __Strings__. So, we have to "import" them first wiht the `use` keyword then use the `new` method to create a new one. In the Example below, I'me creating a `map` __HashMap__ with the keys of type `String` and the values of type `u8`. It should represent the scores that students have obtained after taking and exam. I then inserted the scores of two students, `Bob` and `Ben`:
```rust
use std::collections::HashMap;
fn main() {
    // Create a Hash Map
    let mut map: HashMap<String, u8> = HashMap::new();

    map.insert(String::from("Bob"), 30);
    map.insert(String::from("Ben"), 25);
    println!("{map:?}");
}
```
Here, I've used the `insert` method to insert the student names (keys) and there exam scores (values). The `println` output will be:
```
{"Bob": 30, "Ben": 25}
```

### Accessing values in a Hash Map:
To access a certain value in a __Hash Map__, we can use the `get` method wich takes the key as its parameter. The `get` method retun an __Option<&V>__ which will return __Some<&V>__ in case if the key exists or __None__ if it doesn't. For Example, to get `Bob`'s score we can use the follwing code:
```rust
let bob_score = map.get("Bob").copied().unwrap_or(0);
println!("Bob scored {bob_score}");
```
OK, this looks a little strange. Let's break it down.
As I've mentioned, the `get` method returns the __Option<&V>__ enum of a reference for the values type (here it is `&u8`). The `copied` method will return __Option\<V\>__ instead of __Option<&V>__ i.e. __Option\<u8\>__ as I don't want to return a reference of the value. Then the `unwrap_or` method will return `V` if the __Option__ enum is _Some_ or a `default` value that it takes as a parameter. So, `bob_score` will hold eventually the value of the "Bob" key with `u8` type and it will be printed as follows:
```
Bob scored 30
```
> If we haven't used `copied` here, we would write something strange as the paramtere of `unwrap_or` like `unwrap_or(&0)` and `bob_scrore` will be a refrence of the retrieved value.

Another way to access values inside a __Hash Map__ is to use a `for` loop as follows:
```rust
for (key, value) in &map {
    println!("{}: {}", key, value);
}
```
This will print the following as expected:
```
Ben: 25
Bob: 30
```
One more thing before we can leave this section is that __Mash Maps__ takes ownership over the passed keys and values. Look at the follwing example:
```rust
let jane = String::from("Jane");
let jane_score = 29;
map.insert(jane, jane_score);

println!("{jane} scored {jane_score}"); // Error: borrow of moved value: `jane`
```
Here we have declared `jane` as __String__ and her score `jane_score` as __i32__. As we have used the `insert` method, `jane` has "moved" into the __Hash Map__ as it's a __String__ while `jane_score` is "copied" as it's a simple type. The `println` will cause a compilation error as `jane` isn't valid anymore after the usage of `insert`.

### Updating a Hash Map:
We usualy update a __Hash Map__ in one of the following scenarios:
- Overwite the old values
- Only update if the key isn't present
- Update a value based on its old value
Let's see how we can perform each.

#### Overwrite the values:
This is probably the simplest implementation. We just have to use `insert` using a pre-existing key.
```rust
map.insert(String::from("Jane", 29);
let jane_score = map.get("Jane").copied().unwrap();
println!("Jane score before the update {jane_score}");

map.insert(String::from("Jane"), 30);
let jane_score = map.get("Jane").copied().unwrap();
println!("Jane score after the update {jane_score}");
```
Here, we have updated `Jane`'s score from `29` to `30`
```
Jane score before the update 29
Jane score after the update 30
```

#### Only update if the key isn't present:
In this use case, we use the `entry` method that takes a key as a parameter and returns __Entry__ enum representing an entry that could exist or not. Then we use a similarly useful method, `or_insert` that insert the passed value if the entry doesn't exist. Let's look at this example:
```rust
map.entry(String::from("Jane")).or_insert(25);
map.entry(String::from("Windy")).or_insert(25);
println!("{map:?}");
```
Here, we were retrieving the `Jane` and `Windy` entries and inserting `25` as their exam score if they arent present. As `Jane` entry already exist from the last section, it will remain unchanged with the value of `30`. As for `Windy`, because it's a new key, it will be inserted with the value of `25` and the `println` will print the following:
```
{"Ben": 25, "Windy": 25, "Bob": 30, "Jane": 30}
```

#### Update a value based on its old value:
Let's say that we will give all students a 5 points bonus. So, we will have a new value of a certain key that is the result of the addition of its old value and the 5 points bonus. One way to do this is as follows:
```rust
for (_, score) in map.iter_mut() {
    *score += 5;
}
println!("{map:?}");
```
Let's explain what's going on here.
First, I've used the `iter_mut` method that returns "mutable" references of the keys and values of the __Hash Map__ because I want to change the values. And as I don't care about the keys, I've used the underscore in its place in the `for` expression. Now, `score` is a `&mut u8` or a mutable reference of the values of `u8` type. Next, I've used the "de-referencing" operator "*" on `score`. We will have a look at de-referencing later, but let's just say it's a way to get the value that the reference is "referencing". 
> If I didn't use the "*" here, the compilier will complain about not being able to add {integer} refering to the `5` and `&mut u8` (the `score`) as it considers them of different types.

Then I've added the 5 points bonus to the old values.
The result of this will be all the values have increase by 5 from their old values (compare them to the last println output):
```
{"Ben": 30, "Windy": 30, "Bob": 35, "Jane": 35}
```

We have now looked at __Hash Maps__ and saw how they are almost similar to Python's Dictionaries except for the types restrictions.
Next we will look at Rust's Error Handling. See you then 👋