Learning Rust 🦀: 14 - Option Enum: An Enum and Pattern matching use case
Tags: learning, rust, programming, development
Published: 2023-09-15 10:46:23

We will finish off our Enum and pattern matching discussion that was started [here](https://dev.to/fadygrab/learning-rust-12-basic-enum-2mn3) by a case study on the **Option<T>** standard enum. 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 [Twitter](https://twitter.com/fadyGrAb).

## Table of Contents:
- [Problem of **Null**](#problem-of-null)
- [The Option\<T\> Enum](#the-optionlttgt-enum)
- [Using the Option Enum](#using-the-option-enum)
- [If Let: alternate way to pattern matching](#if-let-alternate-way-to-pattern-matching)

### Problem of **Null**:
How many times have you faced bugs in your code due to an "unexpected" null value? I'll answer that, plenty 😁
The fix is easy, in python for example you can write something like that:
```python
if value is None:
    print("A none value!")
```
> *None* and *Null* are the same in Python

But you have to remember to do that for every value (or data structure) that may become - for some reason 🤷‍♂️ - absent or invalid (i.e. Null) which might not be very scalable in a large code base.  
Rust fixes that! by ... removing the Null 😁! Yes, you have read it write, there is no intrinsic Null type in Rust.  
But ...  
There is a standard Enum called "**Option**" (Option<T>) that can be used in this use case.

### The Option\<T\> Enum:
As I've mentioned, Rust has a standard Enum called "Option" which is loaded in the "prelude" (you don't have to load it with the `use` keyword) to deal with the **Null** situation that we have here. It has two variants:
1. Some(T)
2. None

And it is defined like this:
```rust
enum Option<T> {
    Some(T),
    None,
}
```
The first thing to notice is that weird "\<T\>" notation. This is a generic type notation (T for Type) and we will encounter it later on. What's important now is that you imagine "Option" as a "**wrapper**" for *Any Type* that you use in your program that might be either "Some" value or "None" (Null) and as we have learned in the [last article](https://dev.to/fadygrab/learning-rust-13-pattern-matching-basics-40pj), that pattern matching for Enums must be "Exhaustive", when using the Option Enum the compiler will force the Null value handling sparing you from remembering to do that yourself.  

### Using the Option Enum:
The **Option** Enum is load into the Rust program prelude by default so as its variants **Some(T)** and **None** and you don't have to use namespacing to use them.
> Something like `Option<i32>::Some(5)` isn't necessary. Just use `Some(5)`.

```rust
fn main() {
    // Using the Option Enum without importing it.
    let some_number = Some(5);
    let name = Some("My Name");
    let none: Option<i32> = None;
}
```
Here, we are directly using `Some` and `None`. Note that for `Some`, it can take any Type and the compiler can infer that type. But for `None`, we have to set its type.  
The **Option/<T/>** Enum has a set of useful methods like `is_some()` or `is_none()`. See the docs for a complete listing:
```rust
fn main() {
    // Using the Option Enum without importing it.
    let some_number = Some(5);
    let none: Option<i32> = None;

    // Some Option Enum methods
    assert_eq!(some_number.is_some(), true);
    assert_eq!(none.is_none(), false);  // Will cause assertion error
}
```
One important thing to know, is that `Some(5)` isn't `5`! Let's try it out:
```rust
fn main() {
    // Using the Option Enum without importing it.
    let some_number = Some(5);
    let number = 5;

    let addition = some_number + number;
}
```
If we run this code, we will get the following compilation error:
```
error[E0369]: cannot add `{integer}` to `Option<{integer}>`
  --> src/main.rs:13:32
   |
13 |     let addition = some_number + number;
   |                    ----------- ^ ------ {integer}
   |                    |
   |                    Option<{integer}>
```
Basically, what this error message is telling us is that Rust don't know how to add a `5` that is wrapped in Option Enum with a normal `i32` 5 as they are now two different types.  
So how could we do that??🤔  
This is when **Pattern Matching** comes in and the purpose of the **Option Enum** becomes clear.  
We will demo the use of bound data by using a function that doubles what's inside `Some`:
```rust
fn main() {
    // Using the data bound to Some.
    let five = Some(5);

    let mut result = double(five);
    println!("Double of 5 is: {result:?}");

    result = double(none);
    println!("Double of None is: {result:?}");
}

fn double(num: Option<i32>) -> Option<i32> {
    match num {
        Some(n) => Some(n * 2),
        None => None,
    }
}
```
The function is called `double` and it takes an **Option** Enum with `i32` data type and outputs the same type. Inside the function, there is a `match` expression that has to matching arms for the two **Option** variants, **Some** and **None**. And hence pattern matching must be exhaustive, the compiler checks if all variants are mentioned and therefore enforces a "Null" handling arm.  
If we run this code we will get:
```
Double of 5 is: Some(10)
Double of None is: None
```
So, when should we use the **Option** Enum you say?  
We should use it whenever our values could be a Null because as long as the value isn't wrapped in it, Rust assumes that it will be always valid through the program execution.  
The **Option** Enum is yet another way where Rust enforces its safety guard rails and we should use that for our advantage.
![safety first](https://media.giphy.com/media/3oz8xUJsD8AsihJrtC/giphy.gif)

### If Let: alternate way to pattern matching:
One more thing before we leave pattern matching (for now 😉), is the `if let` expression. Image that you are receiving a sensor readings that may be null due to network problems, corrupt data, or for whatever reason and you are interested in printing the value when it's `5` only. One way to do it is as follows:
```rust
fn main(){
    let reading: Option<u8> = Some(5);

    match reading {
        Some(5) => println!("Reading of 5 is received [match]"),
        _ => (),
    }
}
```
Remember the "\_" placeholder from [here](https://dev.to/fadygrab/learning-rust-13-pattern-matching-basics-40pj/#using-the-placeholder)? Here, we are only interested when we receive `Some(5)`. Any other value including Null, will produce the Unit tuple () (basically, do nothing). But this is a bit verbose.  
Another way to write the same code is by using `if let`:
```rust
fn main(){
    let reading: Option<u8> = Some(5);

    if let Some(5) = reading {
        println!("Reading of 5 is received [if let]")
    }
}
```
If we run this code, you will get the same result as before but less verbose. The `if let` syntax is a bit confusing but once you use it often, it will make sense:
```
if let <pattern> = <value> {
   <expression(s) to be executed if matched>
} else {
   <expression(s) to be executed if not matched>
}
```
So, when to use `if let`? There is no advantage of using `if let` over `match` for "one" pattern matching except it's less verbose. So, using one over the other depends on what you are trying to do...  
If you want to take advantage of the exhaustiveness of the `match`, use it. Otherwise, if you are only interested in one match and you can safely ignore all the others, use `if let`.
> As show, `if let` has and `else` clause that that be used as a "catch-all" for all other not matched patterns

Next, we will explore more about how we can use Packages, Crates and Modules in Rust. I may break it into several articles too. See you then 👋.