Enums & Pattern Matching

An enum defines a type that can be one of several named variants, and match forces you to handle every one of them — the compiler itself will refuse to build if you forget a case.

Defining an enum

Rust src/main.rs
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn main() {
    let light = TrafficLight::Red;

    match light {
        TrafficLight::Red => println!("Stop"),
        TrafficLight::Yellow => println!("Slow down"),
        TrafficLight::Green => println!("Go"),
    }
}
Output
Stop

match compares light against each listed pattern in order and runs the matching arm. Unlike a C switch, there's no fallthrough between arms and no need for a break.

match must be exhaustive

Rust src/main.rs — this won't compile
match light {
    TrafficLight::Red => println!("Stop"),
    TrafficLight::Green => println!("Go"),
}
Compiler output
error[E0004]: non-exhaustive patterns: `TrafficLight::Yellow` not covered
  |
  |     match light {
  |           ^^^^^ pattern `TrafficLight::Yellow` not covered

Leaving out Yellow is a compile error, not a bug you find later at runtime. This is one of the most quietly powerful features in Rust: add a new variant to an enum anywhere in a large codebase, and every match on that enum that doesn't already handle it stops compiling until you deal with it — impossible to accidentally forget a case.

Enum variants can carry data

Rust src/main.rs
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => std::f64::consts::PI * radius * radius,
        Shape::Rectangle(w, h) => w * h,
    }
}

fn main() {
    let c = Shape::Circle(2.0);
    let r = Shape::Rectangle(3.0, 4.0);
    println!("{:.2}", area(&c));
    println!("{}", area(&r));
}
Output
12.57
12

Each Shape variant carries exactly the data that variant needs — a circle needs one number, a rectangle needs two. Matching on the enum both identifies which variant you have and unpacks its data into named bindings (radius, w, h) in one step.

if let for the single-case shortcut

When you only care about one specific variant and want to ignore the rest, if let avoids writing a full match with a throwaway catch-all arm:

Rust src/main.rs
fn main() {
    let shape = Shape::Circle(5.0);

    if let Shape::Circle(radius) = shape {
        println!("It's a circle with radius {}", radius);
    }
}
Output
It's a circle with radius 5
Note: Rust's most important enum, Option<T>, is built from exactly this pattern — a value that's either Some(x) or None. You'll meet it properly, along with Result, in the Error Handling lesson; the match and if let skills from this lesson apply directly.