Enumerations

In the previous chapter we looked at the Option type. If we examine the definition of Option we see that it is actually an enumeration:

#![allow(unused)]
fn main() {
pub enum Option<T> {
    None,
    Some(T),
}
}

An enumeration is a type that can have a fixed set of values. In the case of Option, the values are None and Some(T).

Unlike in other languages, Rust's enumerations can hold data. In the case of Option, the Some variant holds a value of type T. T is a generic type parameter, which means that Option can hold any type of value.

Let's look at another example of an enumeration:

#![allow(unused)]
fn main() {
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
}

In this example, Direction is an enumeration with four variants: Up, Down, Left, and Right. Each variant represents a direction.

Imagine we would like to include the velocity of the movement in the Direction enumeration. We can do this by associating data with each variant:

#![allow(unused)]
fn main() {
enum Direction {
    Up(u32),
    Down(u32),
    Left(u32),
    Right(u32),
}
}

The power of enumerations becomes apparent when we use them in combination with match expressions. A match expression allows us to match on the different variants of an enumeration and execute different code based on the variant.

Let's look at an example:

fn main() {
    move_player(Direction::Up(10));
}

enum Direction {
    Up(u32),
    Down(u32),
    Left(u32),
    Right(u32),
}

fn move_player(direction: Direction) {
    match direction {
        Direction::Up(velocity) => println!("Moving up with velocity: {velocity}"),
        Direction::Down(velocity) => println!("Moving down with velocity: {velocity}"),
        Direction::Left(velocity) => println!("Moving left with velocity: {velocity}"),
        Direction::Right(velocity) => println!("Moving right with velocity: {velocity}"),
    }
}

The associated data in the enumeration variants can be different for each variant. For an elevator, we might want to treat the vertical movement differently from the horizontal movement. We can do this by associating different data with the Up and Down variants.

fn main() {
    move_elevator(Direction::Up(10));
}

enum Direction {
    Up(u32),
    Down(u32),
    Left,
    Right,
}

fn move_elevator(direction: Direction) {
    match direction {
        Direction::Up(velocity) | Direction::Down(velocity) => println!("Moving up or down with velocity: {velocity}"),
        Direction::Left | Direction::Right => println!("Call 911!"),
    }
}

C-style Enumerations

Rust also has C-style enumerations, which are similar to enumerations in C. They are defined using the enum keyword without any associated data:

#[repr(u8)]
enum Color {
    Red = 0,
    Green = 1,
    Blue = 2,
}

fn main() {
    println!("Red: {}", Color::Blue as u8);
}

This is a less-commonly used feature in Rust, but it can be useful when you need to map values to integers.