Learning Rust 🦀: 13 - Pattern matching basics
Tags: learning, rust, programming, development
Published: 2023-09-08 14:20:16

We will continue our Enum mini-series. This time we will discuss the pattern matching basics using Enums. 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:
- [The **match** control flow](#the-match-control-flow)
- [Matching with patterns bind to values](#matching-with-patterns-bind-to-values)
- [Using the **"_"** placeholder](#using-the-placeholder)

### The **match** control flow:
Another feature that I couldn't find in Python is the *Pattern Matching*. It's that control flow construct that is based on ... you've guessed it, *patterns* 😁! Let's inspect that with an example:
```rust
fn main(){
    let die_roll = 4;

    if die_roll == 1 {
        println!("one");
    else if die_roll == 2 {
        println!("two");
    else if die_roll == 3 {
        println!("three");
    else if die_roll == 4 {
        println!("four");
    else if die_roll == 5 {
        println!("five");
    else {
        println!("six");
}
```
In this example, we are throwing a die and print the number that we got in letters. Here, we are controlling the program flow based on the **value** of `die_roll` and the `if` expression should evaluate to either `true` or `false`. This is as far as we can go with `if`. We can re-write the previous example using `match` as follows:
```rust
fn main(){
    let die_roll = 4;

    match die_roll {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        4 => println!("four"),
        5 => println!("five"),
        6 => println!("six"),
    }
}
```
This code snippet will work exactly the same as the previous one but with less typing 😉. But `match` doesn't only provide *less typing* benefits over `if`, it can match based on types either and not only values. Take a look at the following example:
```rust
#[derive(Debug)]
enum Superhero {
    Batman,
    Superman,
    WonderWoman,
    Flash,
    GreenLantern,
    CatWoman,
}

fn main() {
    // Basic matching.
    let hero = Superhero::Batman;
    let mut is_batman = false;
    let super_power = match hero {
        Superhero::Batman => {
            is_batman = true;
            "Rich"
        }
        Superhero::Superman => "Flying, Bulletproof, Heat vision, Cold breath",
        Superhero::WonderWoman => "Flying, Strength, Divine weapons",
        Superhero::CatWoman => "Stealth, Agility",
        Superhero::Flash => "Speed, Connection with the speed-force",
        Superhero::GreenLantern => "Flying, Instant light constructs, Lantern Corps",
    };
    println!("{hero:?}: {super_power} -- is batman {is_batman}");
}

``` 
If you run this code, you will get:
```
Batman: Rich -- is batman true
```
Let's break it down. As you can see, we've created our `Superhero` Enum containing my favorite superheroes 😁.
> Note the `#[derive(Debug)]`, remember [this one](https://dev.to/fadygrab/learning-rust-11-debugging-custom-typesstructs-3dnp) 😉? 

Then, we have created `hero` variable that holds the `Superhero` variant *BATMAAAAN* 🦇 and an `is_batman` boolean flag that is initially set to `false`. Then, we've created `super_power` variable that gets its value form a `match` expression depending on the passed `Superhero` variant in `hero`. And Finally, we print what we get.  
Now let's inspect the `match` block. It is constructed by using the `match` keyword then the "Scrutinee" (as described by the docs) or in simpler terms, the variable that we want to match against. Following that in the curly brackets, we have the **"match arm"** which is the value - or more generally the pattern - we are matching followed by the `=>` operation then the **"expression"** we want to execute (or return). Usually, you don't want to use curly bracket in the "expression" block if the expression is short but if your expression block contains more than one expression/statement then their use is mandatory.
> Check out the "BATMAAAN" expression where we have changed the value of `is_batman` to `true` in addition to returning its superpower as `&str` (string literal).  

There are two things to consider when using `match` for pattern matching:
    1. The match patterns must be exhaustive i.e. using all the variants of the checked variable.
    2. The match arms must return the same type i.e. one match arm can't return `i32` and another one returns `&str`.  
Notice that in this example we have listed **all** the superheroes in our `Superhero` Enum and all the match arms are returning string literals.  
Think of the `match` flow as a "Coins sorting machine" that executes the first match arm that matches the tested variable.  

![Coin sorting machine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p56gp81m5rz8xj7lz7oa.gif)
### Matching with patterns bind to values:
In the [last article](https://dev.to/fadygrab/learning-rust-12-basic-enum-2mn3), we saw that we can bind data to Enums and using `match` is a way to use this bound data. Have a look at this example:
```rust
#[derive(Debug)]
enum SuperheroWithWeapon {
    Batman(FavoriteWeapon),
    Superman,
    WonderWoman(FavoriteWeapon),
    Flash,
    GreenLantern(FavoriteWeapon),
    CatWoman,
}

#[derive(Debug)]
enum FavoriteWeapon {
    LassoOfTruth,
    GreenLanternRing,
    Batarang,
}

fn main() {
    // Matching with patterns that bind to values with a catch-all.
    let hero_with_weapon_1 = SuperheroWithWeapon::WonderWoman(FavoriteWeapon::LassoOfTruth);
    match hero_with_weapon_1 {
        SuperheroWithWeapon::Batman(weapon) => {
            println!("Batman: {weapon:?}")
        }
        SuperheroWithWeapon::WonderWoman(weapon) => {
            println!("Wonder Woman: {weapon:?}")
        }
        SuperheroWithWeapon::GreenLantern(weapon) => {
            println!("Green Lantern: {weapon:?}")
        }
        other => println!("{other:?} doesn't usually use weapons."),
    };
}
```
If you run this code, you will get:
```
Wonder Woman: LassoOfTruth
```
Here, we've created two Enums, the `SuperheroWithWeapon` that accepts Superheroes with favorite weapons or without, and the `FavoriteWeapon` Enums that is passed to the previous one if the superhero uses a favorite weapon.
> Notice the #[derive(Debug)] again.

This time, we've created a `hero_with_weapon_1` variable that hold a `WonderWoman` variant of the `SuperheroWithWeapon` Enum and she uses the `LassoOfTruth` as her favorite weapon 🪢. In order to use the data of the favorite weapon that is bound to Wonder Woman, we have "named" it `weapon` in the match arm when we were defining the pattern to match against then we can use it normally in the match arm expression as in 
```rust
SuperheroWithWeapon::WonderWoman(weapon) => {
            println!("Wonder Woman: {weapon:?}")
        }
```
You have noticed that we didn't list all the Enum's variants this time and we only listed `Batman`, `WonderWoman`, `GreenLantern`, and a mysterious `other` placeholder?? Actually, this placeholder serves as a **"catch all"** match arm (or the `else` in the `if` expression). Therefore, our patterns in this example fulfil the match requirement of being exhaustive. Also, notice that all the match arms return the same unit tuple "()" (println! return value).
### Using the **"\_"** placeholder:
Another way to use a "catch all" match arm, is using the "\_" place holder. But this time, we won't use any data in the match arm (similar to Python's "\_" in for loops or tuples expansion).
```rust
#[derive(Debug)]
enum SuperheroWithWeapon {
    Batman(FavoriteWeapon),
    Superman,
    WonderWoman(FavoriteWeapon),
    Flash,
    GreenLantern(FavoriteWeapon),
    CatWoman,
}

#[derive(Debug)]
enum FavoriteWeapon {
    LassoOfTruth,
    GreenLanternRing,
    Batarang,
}

fn main() {
    // Matching with patterns that bind to values with _ placeholder.
    let hero_with_weapon_2 = SuperheroWithWeapon::Superman;
    match hero_with_weapon_2 {
        SuperheroWithWeapon::Batman(weapon) => {
            println!("Batman: {weapon:?}")
        }
        SuperheroWithWeapon::WonderWoman(weapon) => {
            println!("Wonder Woman: {weapon:?}")
        }
        SuperheroWithWeapon::GreenLantern(weapon) => {
            println!("Green Lantern: {weapon:?}")
        }
        _ => println!("Doesn't usually use weapons."),
    };
}
```
If you run this code, you will get:
```
Doesn't usually use weapons.
```
This time we have created `hero_with_weapon_2` as a `Superman` variant which doesn't use any weapons. The `match` in this case will go to the "catch all" arm represented by the "_" placeholder which prints a generic message and doesn't use any data.  

We will revisit "pattern matching" again later. In the next one, we will check an important Enum, the **"Option"** Enum which protects us from **Null** values bugs 🫢. See you then 👋.