Skip to content
Enums and Pattern Matching
step 1/7

Reading — step 1 of 7

Learn

~3 min readStructs, Enums, and Error Handling

Enums in Rust are dramatically more powerful than in C-family languages. Each variant can carry its own data, making enums algebraic data types — perfect for modeling "this OR that OR the other." Chapter 6 of the Rust Book treats this as core language territory.

The basic form

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

let d = Direction::North;

Like a C enum. Each variant is a value of the type.

Variants with data

This is where Rust enums shine:

enum Shape {
    Circle(f64),                          // tuple-style — one f64
    Rectangle { width: f64, height: f64 }, // struct-style — named fields
    Triangle(f64, f64, f64),               // three sides
    Empty,                                  // no data
}

let s1 = Shape::Circle(5.0);
let s2 = Shape::Rectangle { width: 3.0, height: 4.0 };
let s3 = Shape::Triangle(3.0, 4.0, 5.0);
let s4 = Shape::Empty;

Each variant is structurally distinct. The enum's value tells you both WHICH variant and its associated data.

Pattern matching with match

match deconstructs enum values, exhaustively:

fn area(s: &Shape) -> f64 {
    match s {
        Shape::Circle(r) => 3.14 * r * r,
        Shape::Rectangle { width, height } => width * height,
        Shape::Triangle(a, b, c) => {
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
        Shape::Empty => 0.0,
    }
}

The compiler enforces exhaustiveness. Add a new variant → every match block on Shape lights up at compile time. Free refactoring safety.

if let — single-pattern matching

When you only care about ONE variant:

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

Cleaner than a match with one arm and a _ => {}.

while let — loop with a pattern

let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
    println!("got {}", top);
}

Pops until the vector is empty (returns None).

Pattern matching extras

match n {
    0 => println!("zero"),
    1 | 2 | 3 => println!("small"),    // multiple patterns
    4..=10 => println!("medium"),       // range
    n if n > 100 => println!("big: {}", n),  // guard
    _ => println!("other"),              // catch-all
}

Why ADTs matter

C's enums tell you "one of N integers." Rust's enums tell you "one of N possibilities, each with its own structured data." This collapses common patterns:

  • Result types — Ok(value) or Err(error)
  • Tree nodes — Leaf or Branch(left, right, value)
  • Parser states — Token(string) or Symbol(int) or Punctuation(char)
  • HTTP responses — Json(value) or Text(string) or Binary(bytes) or Empty

Building APIs around enums + match makes invalid states unrepresentable.

Common mistakes

  • Forgetting catch-all _ => in non-exhaustive matches — compile error guides you.
  • Using if let when match would express intent better — match is clearer for multi-variant cases.
  • Trying to use enum variants as type parameters — variants aren't types. The whole enum IS the type; variants are values.
  • Unwrapping Option without checking.unwrap() panics on None; prefer pattern matching or ? (next lesson).

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…