Rust path · Types that do the work · Topic 6 of 12

Enums, Option and pattern matching

Not started

A Rust enum is a type whose value is exactly one of several **variants**, and each variant can carry its own data. Together with match, which must handle every variant, this is how Rust models "one of these things": a parsed command, a network message, a state in a state machine, or a value that might be missing. The standard library's two most important types, Option and Result, are ordinary enums.

Why it matters: Rust has no null and no unchecked variants. Enums and exhaustive matching turn whole classes of bugs, a forgotten case or a missing value, into compile errors.

  1. 1Variants can carry data

    Each variant of an enum can hold its own fields. A value is exactly one variant at a time, and the compiler knows which ones exist.

  2. 2match is exhaustive

    A match must handle every variant. Add a variant later and every match that forgot it stops compiling.

  3. 3Option instead of null

    A value that may be absent has type Option<T>: Some(v) or None. You can't use the T without first handling None.

  4. 4Lighter forms

    if let, let ... else and (since Rust 1.88, edition 2024) let chains handle one pattern without a full match.

1

How it works

The explanation, with examples you can run.

Data in variants, and exhaustive match

#[derive(Debug)]enum Shape {    Circle { r: f64 },    Rect { w: f64, h: f64 },    Point,} fn area(s: &Shape) -> f64 {    match s { // must cover every variant, or it does not compile        Shape::Circle { r } => std::f64::consts::PI * r * r,        Shape::Rect { w, h } => w * h,        Shape::Point => 0.0,    }} fn parse_port(s: &str) -> Option<u16> {    let n: u16 = s.parse().ok()?; // None if it is not a number    if n == 0 { None } else { Some(n) }} fn main() {    for s in [Shape::Circle { r: 1.0 }, Shape::Rect { w: 2.0, h: 3.0 }, Shape::Point] {        println!("{s:?} -> {:.2}", area(&s));    }    let Some(port) = parse_port("8080") else {        panic!("bad port");    };    if let Some(p) = parse_port("443") && p < 1024 { // let chains: Rust 1.88+, edition 2024        println!("{port} and privileged {p}");    }    println!("{:?} {:?}", parse_port("0"), parse_port("http"));}

Output: three lines of areas (3.14, 6.00, 0.00), then 8080 and privileged 443, then None None.

In memory an enum is a tag plus enough space for its largest variant. The compiler often hides the tag in spare bits: Option<&T> and Option<Box<T>> are the same size as a plain pointer.

Patterns

Patterns appear in match, let, if let, while let, for and function parameters. They can:

  • destructure structs, tuples, enums and slices ([first, .., last]);
  • bind parts to names, by value or with ref, and with name @ pattern to keep the whole;
  • match ranges and alternatives (1..=9, 'a' | 'e');
  • add guards: Some(n) if n > 10 =>. Since Rust 1.95 guards can also be if let.

The compiler checks that a match is exhaustive and warns about arms that can never be reached.

Option instead of null

Rust has no null. An absent value is None, and to use the value inside a Some you must say what happens otherwise: match, if let, unwrap_or(default), map, and_then, or ? to return None early, as parse_port does. unwrap() and expect("why") are there for when absence really is a bug; they panic.

One pattern at a time

  • if let Some(x) = opt { ... } handles one variant and ignores the rest.
  • let Some(x) = opt else { return ... }; (Rust 1.65) binds or bails out, keeping the main path unindented.
  • Let chains (Rust 1.88, 2024 edition only): if let Some(p) = a && let Ok(v) = p.parse::<u32>() && v > 3 { ... } combines patterns and conditions without nesting.

Prefer an enum over a set of boolean flags or a struct with optional fields that are only valid in some combinations: if a state can't be represented, it can't occur.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You add a Triangle variant to enum Shape. What happens to a match on Shape that has no wildcard arm?
  2. 2.How is Option<T> different from a nullable pointer?
  3. 3.What does let Some(port) = parse_port(s) else { return; }; do?

0 of 3 right so far. Get all three to finish this topic.