Rust Enums and Pattern Matching

An enum (short for enumeration) defines a type that can be one of several named options. Each option is called a variant. Enums are one of Rust's most powerful features — they can carry data and work perfectly with pattern matching.

Defining an Enum

enum Direction {
    North,
    South,
    East,
    West,
}

A value of type Direction can only be one of these four options at any time.

fn main() {
    let heading = Direction::North;

    match heading {
        Direction::North => println!("Going north"),
        Direction::South => println!("Going south"),
        Direction::East  => println!("Going east"),
        Direction::West  => println!("Going west"),
    }
}

Output:

Going north

The Traffic Light Diagram

enum Light { Red, Yellow, Green }

Light::Red    → "Stop"
Light::Yellow → "Slow down"
Light::Green  → "Go"

A traffic light is always exactly one of these three states.

Enums with Data

Each variant can carry its own data. Different variants can carry different types and amounts of data:

enum Shape {
    Circle(f64),            ← One value: radius
    Rectangle(f64, f64),   ← Two values: width, height
    Triangle,              ← No data
}
fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle(r)       => 3.14 * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle        => 0.0,
    }
}

Pattern Matching with match

The match expression compares a value against a list of patterns and runs the code for the first pattern that matches. Every possible case must be handled.

fn describe_number(n: i32) {
    match n {
        1          => println!("One"),
        2 | 3      => println!("Two or three"),
        4..=9      => println!("Between 4 and 9"),
        _          => println!("Something else"),  ← Catch-all
    }
}

Binding Values in Patterns

Extract data from an enum variant while matching it:

enum Coin {
    Penny,
    Quarter(String),   ← Carries a state name
}

fn value(coin: Coin) -> u32 {
    match coin {
        Coin::Penny              => 1,
        Coin::Quarter(state) => {
            println!("Quarter from {}.", state);
            25
        }
    }
}

The Catch-All Pattern

Use _ as the last arm to catch all values you do not explicitly handle:

match number {
    1 => println!("One"),
    2 => println!("Two"),
    _ => println!("Other"),    ← Handles 3, 4, 5, and everything else
}

Rust requires that every possible value is handled. If you miss a case, the compiler will tell you.

if let — Match a Single Variant

When you only care about one variant and want to ignore the rest, if let is a shorter alternative to match:

let shape = Shape::Circle(5.0);

if let Shape::Circle(r) = shape {
    println!("Circle with radius {}", r);
}

This is identical to a match with one arm and a _ catch-all, but written more concisely.

if let with else

if let Shape::Circle(r) = shape {
    println!("It is a circle with radius {}", r);
} else {
    println!("It is not a circle");
}

Enums in the Standard Library

Rust's standard library uses enums heavily. You will see Option and Result in almost every Rust program. Both are enums. Learning enums well prepares you to understand those types fully in later topics.

Common Enum Patterns

Pattern                    Use Case
-------                    --------
Simple variants            State machines, categories, directions
Variants with data         Events with payloads, shapes with dimensions
match + all variants       Handle every case explicitly
match + _                  Handle some cases, ignore the rest
if let                     Handle one specific case only

Quick Reference

enum Name { A, B, C }                ← Define an enum
let x = Name::A;                     ← Create a value
match x { Name::A => ..., _ => ... } ← Match all cases
if let Name::A = x { ... }           ← Match one case
enum Msg { Quit, Move { x: i32 } }   ← Variants with data

Leave a Comment