Get Started with Rust: Enums
Tags: rust
Published: 2022-11-08 14:25:57

---
title: Get Started with Rust: Enums
published: true
date: 2022-10-25 00:00:00 UTC
tags: rust
canonical_url: https://serokell.io/blog/enums-and-pattern-matching
---

# Get Started with Rust: Enums

There are two ways to create custom data types in Rust: structs and enums.

In contrast to [structs](https://serokell.io/blog/structs-in-rust), enums construct a type that has multiple variants and not multiple fields.

While structs are a part of pretty much any programming language, enums are not so mainstream and are mostly seen in statically typed functional languages like Haskell or OCaml. But, as we’ll see, they are quite nice for domain modeling with types.

This article will show you how to use enums in Rust. By the end of the article, you’ll know the answers to these questions:

- How to define and instantiate an enum?
- When to use enums instead of structs?
- What is pattern matching, and why is it so useful?
- What are the `Option` and `Result` enums, and how to use them?

## What is an enum in Rust?

Enums (short for enumerations) are a way to create compound data types in Rust. They let us _enumerate_ multiple possible variants of a type.

For example, we can use enums to recreate a `Bool` data type with two variants: `True` and `False`.

```
// [1]  
enum Bool {
// [2]
    True,
    False,
}

// [1]: The name of the data type.
// [2]: The variants of the data type. 

```

We can get a value of this type by constructing it with one of the two variants.

```
let is_true = Bool::True;
let is_false = Bool::False;

```

Enum variants are like structs: they can contain fields, which can be unnamed or named. In the example below, the `Alive` variant contains an unnamed signed 8-bit integer.

```
enum HealthBar {
    Alive(i8),
    Dead,
}

let is_alive = HealthBar::Alive(100);
let is_dead = HealthBar::Dead;

```

If we would like to signify that the field represents life points, we can use a named “life” field instead:

```
enum HealthBar {
    Alive{life: i8},
    Dead,
}

let is_alive = HealthBar::Alive{life: 100};
let is_dead = HealthBar::Dead;

```

Rust enums might be familiar to you from other languages in the guise of [sum types](https://serokell.io/blog/algebraic-data-types-in-haskell#sum-types) (Haskell and other FP languages) or [discriminated unions](https://blog.logrocket.com/understanding-discriminated-union-intersection-types-typescript/). Together with structs, they create a simple yet highly usable mechanism for encoding a lot of information in your types.

### Enums vs. structs

A struct has multiple fields. In contrast, an enum has multiple variants.

But, while the concrete value of a struct type has multiple fields, a concrete value of an enum type is exactly one variant.

Enums enable you to build clearer types and decrease the number of illegal states that your data can take.

Let’s look at an example. We want to model a character in a game.

It can have three states:

- Alive. In this case, it has life points.
- Knocked out. In this case, it has life points and a counter of turns it needs to wait to regain consciousness.
- Dead. In this case, it doesn’t need any additional fields.

Modeling it with only a struct is quite awkward. But we can try the following:

```
struct Player {
    state: String,
    life: i8,
    turns_to_wait: i8,
}

```

Here, we store the name of the state in a string. If the character is “dead”, their life should be 0. And if they are “knocked out”, they should have some turns to wait until they can act.

```
let dead_player = Player {
    state: "dead".to_string(),
    life: 0,
    turns_to_wait: 0,
};

let knocked_out_player = Player {
    state: "knocked out".to_string(),
    life: 50,
    turns_to_wait: 3,
};

```

This works, but it’s very wonky and obscures the states in type. You need to read the code or docs to know the available states, which diminishes the value of types as documentation. You can also use any kind of string in your code, such as “daed” or “knacked out”, without a compiler error.

Using an enum, we can list all the possible states in a readable manner.

```
enum Player {
    Alive{life: i8},
    KnockedOut{life: i8, turns_to_wait: i8},
    Dead,
}

let dead_player = Player::Dead;
let knocked_out_player = Player::KnockedOut { 
    life: 50, 
    turns_to_wait: 3, 
};

```

## Pattern Matching

Enum types can have one of multiple variants. So if a function takes an enum, we need a way to adapt the function’s behavior depending on the variant of data it will encounter.

This is done with pattern matching. If variants _construct_ values of a type, pattern matching _deconstructs_ them.

Recall our `Bool` data type:

```
enum Bool {
    True,
    False,
}

```

Suppose we want to create a function called `neg` that returns the opposite of the boolean that we provide.

This is how we can do it in Rust using pattern matching.

```
fn neg(value: Bool) -> Bool {
// [1]
    match value {
// [2] [3]
        Bool::True => Bool::False,
        Bool::False => Bool::True,
    }
}

// [1]: The value we’re pattern matching on.
// [2]: Pattern we want to match. 
// [3]: Result.

```

The result of a match statement can be either an expression or a statement.

```
fn print_value(value: Bool) {
    match value {
        Bool::True => println!("Clearly true!"),
        Bool::False => println!("Unfortunately false!"),
    }
}

```

If you want the result to be more than a single statement or expression, you can use curly braces.

```
fn print_and_return_value(value: Bool) -> Bool {
    match value {
        Bool::True => {
            println!("Clearly true!");
            Bool::True
        }
        Bool::False => {
            println!("Unfortunately false!");
            Bool::False
        }
    }
}

```

While this basic case highly resembles a case/switch statement, pattern matching can do much more. We can use pattern matching to assign values _while_ choosing the code path to execute. This enables us to easily work with nested values.

```
fn take_five(player: Player) -> Player {
    match player {
        Player::Alive { life: life } => Player::Alive { life: life - 5 },
        Player::KnockedOut {
            life: life,
            turns_to_wait: turns_to_wait,
        } => Player::KnockedOut {
            life: life - 5,
            turns_to_wait: turns_to_wait,
        },
        Player::Dead => Player::Dead,
    }
}

```

To make the code example above even simpler, we can use the [field init shorthand](https://serokell.io/blog/structs-in-rust#field-init-shorthand). It lets us substitute `life:life` with `life` in pattern matching and construction of structs.

```
fn take_five(player: Player) -> Player {
    match player {
        Player::Alive { life } => Player::Alive { life: (life - 5) },
        Player::KnockedOut {
            life,
            turns_to_wait,
        } => Player::KnockedOut {
            life: (life - 5),
            turns_to_wait,
        },
        Player::Dead => Player::Dead,
    }
}

```

The code above has one problem: it doesn’t take into account the fact that a player will die if their health reduces to 0. We can fix that with the help of [match guards](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#extra-conditionals-with-match-guards). and [wildcards](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#ignoring-an-entire-value-with-_).

- **Match guards** enable you to add `if` conditions to the pattern. So we can make the pattern match if the life of `Player::Alive` is larger than 5, for example.

- If you put a **wildcard** (marked by underscore) in a pattern, it will match anything. It can be used at the end of a match statement to handle all the remaining cases.

```
fn take_five_advanced(player: Player) -> Player {
    match player {
        Player::Alive { life } if life > 5 => Player::Alive { life: (life - 5) },
        Player::KnockedOut {
            life,
            turns_to_wait,
        } if life > 5 => Player::KnockedOut {
            life: (life - 5),
            turns_to_wait,
        },
        _ => Player::Dead,
    }
}

```

The match statement must be exhaustive – it needs to cover all the possible values. If you fail to cover some of the options, the program won’t compile.

For example, if we didn’t use the underscore at the end of the statement, we would have two states that are not covered: `Alive` and `KnockedOut` with less than 5 life points. The compiler can detect this and reject the code.

```
//error[E0004]: non-exhaustive patterns: `Alive { .. }` and `KnockedOut { .. }` not covered

fn take_five_advanced(player: Player) -> Player {
    match player {
        Player::Alive { life } if life > 5 => Player::Alive { life: (life - 5) },
        Player::KnockedOut {
            life,
            turns_to_wait,
        } if life > 5 => Player::KnockedOut {
            life: (life - 5),
            turns_to_wait,
        },
        Player::Dead => Player::Dead,
    }
}

```

## Enum methods and traits

Like structs, enums allow for the creation of associated methods and the implementation of traits. They are a powerful tool for clearing up your code and reducing boilerplate.

### Defining methods for enums

Take a look at the negation function that we implemented earlier.

```
fn neg(value: Bool) -> Bool {
  match value {
    Bool::True => Bool::False,
    Bool::False => Bool::True,
  }
}

```

It would be better to have it be a method so that we can access it via dot-notation.

```
let is_true = Bool::True;
let not_true = is_true.neg();

```

To do that, we need to create an implementation block for Bool. In it, we use the `self` keyword to refer to the `Bool` value at hand. `Self` stands for the type we’re writing the implementation for.

```
impl Bool {
  fn neg(self) -> Self {
    match self {
      Self::True => Self::False,
      Self::False => Self::True,
    }
  }
}

```

### Defining traits for enums

Like structs, enums can also have [traits](https://doc.rust-lang.org/book/ch10-02-traits.html) – Rust’s version of interfaces that enable common functionality among types.

You can derive common traits such as [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html), [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html), and [`Eq`](https://doc.rust-lang.org/std/cmp/trait.Eq.html) using the [`derive`](https://doc.rust-lang.org/reference/attributes/derive.html) attribute.

```
#[derive(Debug, Eq, PartialEq)]
enum Bool {
  True,
  False,
}

```

For example, after deriving `Debug` and `PartialEq`, we can now print and compare values of `Bool`.

```
let is_true = Bool::True;
let is_false = Bool::False;
println!("{:?}", is_true); // Prints “True”. 
let are_same = is_true == is_false; // false 

```

It’s also possible to create your own custom traits and implementations. For more info on this, you can read the [trait section](https://doc.rust-lang.org/book/ch10-02-traits.html) of the Rust book.

## `Option` and `Result` enums

Two enums you will frequently encounter in Rust are [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html), Rust’s safe alternative to `null`, and [`Result`](https://doc.rust-lang.org/std/result/), Rust’s safe alternative to exceptions.

Where can they be used? Well, sometimes a function is not able to return a result for a given input. As a simple example, you cannot divide a number by 0.

```
let impossible = 4 / 0;

```

In fact, Rust doesn’t let you compile a line of code that divides by zero. But you can still do it in multiple other ways.

```
fn main() {
  println!("Choose what to divide 4 with!");
  let mut input = String::new();
  std::io::stdin().read_line(&mut input).unwrap();
  let divisor: i32 = input.trim().parse().unwrap();

  let result = 4 / divisor;
}

```

But what if we don’t want the program to crash whenever we encounter a division by zero? We can instead make the function return the `Option` type and handle the problem further down the code.

`Option` has two variants: `None` or `Some`. The first represents a lack of output – something you would commonly use `null` or `nil` for. `Some` can wrap any type and signifies that the function has successfully returned a value.

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

```

For example, we can wrap division in `Option` so it doesn’t panic when we divide by 0.

```
fn safe_division(a: i32, b: i32) -> (Option<i32>) {
    match b {
        0 => None,
        _ => Some(a / b),
    }
}

let possible = safe_division(4, 0); // None

```

`Result` is similar, but it returns the error encountered instead of returning `None`.

```
enum Result<T, E> {
   Ok(T),
   Err(E),
}

```

Here’s how `safe_division` looks with `Result`:

```
#[derive (Debug, Clone)]
struct DivideByZero; // custom error type

fn safe_division_result(a: i32, b: i32) -> (Result<i32, DivideByZero>) {
    match b {
        0 => Err(DivideByZero),
        _ => Ok(a / b),
    }
}

let also_possible = safe_division_result(4, 0); // Err(DivideByZero)

```

As you can see, we created a custom error type for the error. It’s possible to use strings to denote errors, but it’s not recommended.

### `Option` and `Result` methods

Both of these enums are useful, but they tend to complicate the code.

Here are some commonly used methods that will make working with them easier. They work on both `Option` and `Result` types.

#### `unwrap`

The straightforward way to get the value inside an `Option` or a `Result` is to call `unwrap` on it.

This method has a big downside, though. Calling it on a `None` or `Error` will panic the program, defeating the purpose of error handling.

let some = safe\_division(6, 3); // Some(2) let none = safe\_division(4, 0); // None

let two = some.unwrap(); // 2 let oops = none.unwrap(); // thread ‘main’ panicked at ‘called `Option::unwrap()` on a `None` value’

Therefore, its use is generally discouraged. It can come in handy when you’re writing prototypes, using Rust for simple scripts, or trying out libraries.

#### `unwrap_or_else`

A safe way to unwrap values is to use the `unwrap_or_else` method.

It takes a closure (anonymous function) as an argument. If it runs into `None` or `Err`, it uses this closure to compute a value to use instead of panicking.

For example:

```
let two = Some(2).unwrap_or_else(|| {0}); // 2
let safe = None.unwrap_or_else(|| {0}); // 0

```

There are two more unwrap variants that you can use:

- `unwrap_or`, which lets you provide a value directly. The difference between it and `unwrap_or_else` is that it calculates the default value anytime you run the function, while `unwrap_or_else` calculates it only when necessary.
- `unwrap_or_default`, which provides a default value instead of panicking if the type has a `Default` trait implemented.

#### `?`

The [question mark (try)](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html) operator is a way to get values out of `Option` and `Result` without handling the `None` case.

You can write it at the end of any function call that returns `Option` or `Result` that _is inside_ a function block that returns `Option` or `Result`.

If it encounters a success state, it will extract the value and proceed with the function. If it encounters a fail state (`None` or `Err` of some sort), it will short-circuit the function and return the result.

For `Option`, it is approximately equivalent to:

```
match expr {
    Some(x) => x,
    None => return None,
}

```

Let’s look at an example: a function that performs safe division twice.

```
fn safe_division_twice(a: i32, b: i32, c: i32) -> Option<i32> {
    let result = safe_division(a, b)?;
    safe_division(result, c)
}

let one = safe_division_twice(4, 2, 2); // Some(1)
let oops = safe_division_twice(4, 0, 2); // None

```

It can be very useful when chaining multiple functions that use one of these enums. The alternative of `safe_division_twice` with `match` is quite wordy.

```
fn safe_division_twice(a: i32, b: i32, c: i32) -> Option<i32> {
    match safe_division(a, b) {
        Some(x) => safe_division(x, c),
        None => None,
    }
}

```

But it would become even worse when we need to chain more than two of these functions: each operation requires its own nested match statement.

# How a `safe_division_thrice` function looks with `match` vs. `?`

_With pattern matching_

```
fn safe_division_thrice(a: i32, b: i32, c: i32, d: i32) -> Option<i32> {
    match safe_division(a, b) {
        Some(x) => match safe_division(x, c) {
            Some(y) => safe_division(y, d),
            None => None,
        },
        None => None,
    }
}

```

_With the question mark operator_

```
fn safe_division_thrice(a: i32, b: i32, c: i32, d: i32) -> Option<i32> {
    let first = safe_division(a, b)?;
    let second = safe_division(first, c)?;
    safe_division(second, d)
}

```
#### `and_then` 

Another way to chain these enums without writing deep pattern matching statements is the [`and_then`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap/and_then.html) combinator.

```
let one = safe_division(4, 2)
    .and_then(|x| safe_division(x, 2));

let oops = safe_division(4, 0)
    .and_then(|x| safe_division(x, 2));

```

It extracts the wrapped value and then applies the function provided as its argument to it. In case the value is `None` or `Error`, it will short circuit the function and return that value.

In our case, it will extract the result of the first function. Then, it will take that result and pass it to the anonymous function we provided, which is the same as safe\_division, but with the second argument already provided.

## Conclusion

In this article, we covered the basics of enums in Rust. We looked at what they are and how they can be useful. We also looked at pattern matching, a powerful tool that can be used with enums. We also briefly touched two common enums – `Option` and `Result` – and the methods that you can use to handle them.

If you would like to read more beginner-friendly articles about Rust, be sure to follow us on [Twitter](https://twitter.com/serokell) or subscribe to our newsletter via the form below.