Rust: Enums and Pattern Matching
Tags: rust, programming, beginners, showdev
Published: 2020-02-29 15:00:36

This blog covers concepts related to the following Rust topics:

- `Enum`
- Pattern matching

> the code is available on [GitHub](https://github.com/abhirockzz/learning-rust)

## enum

An `enum` is similar to `struct` in the sense that it is a type for which you can have multiple variants (same as that of a class and it's an instance). But the variants of an `enum` are fixed and defined within the `enum` itself. 

A `Player` might be represented by a `struct`:

```
struct Player {
    name: String,
    rank: i32,
}
```

You can instantiate multiple instances of a `Player`. But lets look at a type which is a little more niche

```
enum PlayerAccountType {
    Free,
    Paid,
}
```

Imagine you have only two account types - a free and a paid one. You can use an `enum` to represent this concept. And, since it just another type, you can use it in the `Player` struct

```
struct Player {
    name: String,
    rank: i32,
    acc_type: PlayerAccountType,
}
```

> you can represent `PlayerAccountType` type with a `struct`, but don't need to since its possible variants are already known

You can reference enum variants using `::`. For e.g. to instantiate a `Player` who has a paid account:

```
let paid_member = Player{acc_type: PlayerAccountType::Paid, name: String::from("john"), email: String::from("john@doe.com")};
```

It's also possible to add data to `enum` variants. For e.g.

```
enum foo {
    foo1(String),
    foo2(String),
}
```

> Note:
> - In addition to data, `enum`s can also have methods
> - [`Option<T>`](https://doc.rust-lang.org/std/option/enum.Option.html) and [`Result<T,E>`](https://doc.rust-lang.org/std/result/enum.Result.html) are widely used `enum`s and are part of the Rust standard library

## Pattern matching

Rust provides the `match` keyword which behaves the same way as a `switch` statement would (Rust does not have `switch`). Before exploring `match`, let's see a simple `if-else-if` example:

```
    let num = 101;
    if num < 100 {
        println!("{} is less than 100", num)
    } else if num > 100 {
        println!("{} is more than 100", num)
    } else if num == 100 {
        println!("{} is equal 100", num)
    }
```

No surprises here! But you *cannot* write this using `match`

```
    match num {
        num < 100 => {
            println!("{} is less than 100", num);
        }
        num < 100 => {
            println!("{} is more than 100", num);
        }
        num == 100 => {
            println!("{} is equal to 100", num);
        }
    }
```

This is *not* possible because the `match` operator needs a value against which it can execute the match process. It does not execute a given expression for you. 

> Update: looks like [`Guards`](https://doc.rust-lang.org/stable/rust-by-example/flow_control/match/guard.html) can help with the above `if-else-if` scenario. Thanks to [Richard](https://medium.com/@rpring9) for pointing this out!

The general format is as follows:

- you have a value you want to match against and a bunch of possible options, also known as a `match arm`
- each arm is a combination of the pattern to match on and it's corresponding `expression` to be run if the match is successful

All this sounds abstract, so let's look at an example of `match`ing with an `enum`

### Match enum

Let's start by defining an `enum`:

```
enum Choice {
    One,
    Two,
    Three,
}
```
.. and match against `Choice`s

```
    //suppose this was passed by the user and you stored in a variable
    let choice = Choice::One; 
    match choice {
        Choice::One => println!("Option 1"),
        Choice::Two => println!("Option 2"),
        Choice::Three => println!("Option 3"),
    }
```

Here, the variable `choice` is the value being `match`ed followed by three `match` arms. For each arm, there is a pattern e.g. `Choice::One` and the corresponding expression e.g. `println!("Option 1")` separated by a `=>`

> In this case, the output will be `Option 1`

### Match an Option

Rust standard library provides the `Option` `enum` whose purpose is to define a type to represent a scenario where you *may or may not* have a value. This makes the code obvious and is a better choice than using `null`, `nil` or similar options to denote the absence of a value

> This is similar to [`Optional` in `Java`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Optional.html)

```
pub enum Option<T> {
    Some(T),
    None,
}
```

> Don't worry about the `T` symbol. Just understand that it is a `generic` type parameter to allow the `enum` to work with various types rather than tying it to a specific one e.g. a `String`

Let's understand this with an example. This is a simple CLI program that accepts an argument from the user (the name) and uses it to display a greeting. If the user does not pass anything, it uses a default greeting. This is a reasonable example of how to use `Option` since we may or may not have an argument passed in by the uses (it's optional! duh!)

Here is ther corresponding function:

```
fn parse_name_arg() -> Option<String> {
    let args: Vec<String> = std::env::args().collect();
    if args.len() == 1 {
        None
    } else {
        let name = &args[1];
        Some(String::from(name))
    }
}
```

`parse_name_arg` function converts passed in arguments into a vector (`Vec<String>`) and returns an `Option`. If an argument was not passed, it returns `None` or `Some` (with the value) - both are variants of an `Option`. Now we can use this function as such:

```
    let name = parse_name_arg();
    match name {
        Some(n) => println!("Hello {}!", n),
        None => println!("Hello there!"),
    }
```

We store the result of `parse_name_arg` in a variable called `name` (which is an `Option`), match different possibilities and execute the greeting accordingly. If you were to pass `john` as an argument, you will get back `Hello john!`. If you dont pass an argument, you will get back a generic greeting `Hello there!`

> To try this out, just [clone the GitHub repo](https://github.com/abhirockzz/learning-rust), change to the correct directory i.e. `cd learning-rust/enums-match` and use `cargo run`. To pass an argument, you can use `cargo run <your argument`

The value of an enum is available in the `match` clause (also called `arm`). This is why we were able to extract the passed in an argument using `Some(n) => println!("Hello {}!", n),`

**Using let with match**

How about storing the greeting in a variable? You can use `let` with `match` to return a value rather as well. Let's tweak the program a little bit

```
    let greeting = match name {
        Some(n) => n,
        None => String::from("there"),
    };
    println!("Hello {}!", greeting)
```

We stored the result of the match in a variable called greeting and used with the `println!` macro which makes the program much simpler!

### Exhaust your choices!

By default, `match` requires you to fulfill or account for *all* the possible options. Let's look at an example. Here is yet another `enum` whose value we will match against

```
enum Days {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday,
}
```

and this is how we want to match (in this case, we happen to be interested in `Friday` only)

```
    let today = Days::Friday;
    match today {
        Days::Friday => println!("thank god its Friday!"),
    }
```

But this will not compile. You will see an error as such:

```
error[E0004]: non-exhaustive patterns: `Monday`, `Tuesday`, `Wednesday` and 3 more not covered
```

To get around this problem, we can use the `_` placeholder:

```
    match today {
        Days::Friday => println!("thank god its Friday!"),
        _ => (),
    }
```

This simply *ignores* the other possibilities. In such situations, you can also use `if let` to keep it concise

```
    let today = Days::Monday;
    if let today = Days::Monday {
        println!("its Monday already! :(");
    }
```

Don't forget to check out these resources:

- [The Rust Programming Language](https://doc.rust-lang.org/book/#the-rust-programming-language) book
- [Rust standard library](https://doc.rust-lang.org/std/) documentation
- [Rust by Example](https://doc.rust-lang.org/stable/rust-by-example/)

That's all for a quick tour of `enum`s and how you can use pattern matching with them. Stay tuned for more!