Skip to content
AgileLogics
7 min read

Enums & match

Prerequisites: Methods & impl blocks

An enum (enumeration) defines a type that can be one of several variants. Where a struct says "this and that", an enum says "this or that".

Syntax

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

enum Shape {
    Circle(f64),                      // variant holding one value
    Rectangle { width: f64, height: f64 }, // variant with named fields
}

let d = Direction::North;
let s = Shape::Circle(1.5);

Unlike enums in many languages, Rust's variants can carry data, and each variant can hold different kinds of data. Enums can have impl blocks and methods, just like structs.

match compares a value against a series of patterns and runs the code for the first one that matches. It's an expression, so it can produce a value. Matches are exhaustive: the compiler checks that every possible variant is handled. Use _ as a catch-all pattern for "anything else".

Option<T> is an enum from the standard library that replaces null:

enum Option<T> {
    Some(T), // there is a value
    None,    // there is no value
}

Because an Option<i32> is a different type from an i32, the compiler makes you handle the None case before you can use the value — you can't forget a null check. Option, Some, and None are available without any use.

if let is shorthand for a match where you only care about one pattern:

if let Some(n) = maybe_number {
    println!("got {n}");
}

Example

use std::f64::consts::PI;

enum Shape {
    Circle(f64),
    Rectangle { width: f64, height: f64 },
    Triangle(f64, f64),
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => PI * r * r,
            Shape::Rectangle { width, height } => width * height,
            Shape::Triangle(base, height) => 0.5 * base * height,
        }
    }
}

fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}

fn main() {
    let shapes = [
        Shape::Circle(1.0),
        Shape::Rectangle { width: 2.0, height: 3.0 },
        Shape::Triangle(4.0, 5.0),
    ];
    for shape in &shapes {
        println!("area: {}", shape.area());
    }

    let number = 7;
    let size = match number {
        0 => "zero",
        1..=9 => "small",
        _ => "large",
    };
    println!("{number} is {size}");

    match divide(10, 2) {
        Some(result) => println!("10 / 2 = {result}"),
        None => println!("cannot divide by zero"),
    }
    match divide(1, 0) {
        Some(result) => println!("1 / 0 = {result}"),
        None => println!("cannot divide by zero"),
    }

    if let Some(result) = divide(9, 3) {
        println!("9 / 3 = {result}");
    }

    let missing = divide(5, 0);
    println!("with default: {}", missing.unwrap_or(0));
}

unwrap_or(0) returns the value inside Some, or 0 for None.

Expected output

area: 3.141592653589793
area: 6
area: 10
7 is small
10 / 2 = 5
cannot divide by zero
9 / 3 = 3
with default: 0

Common mistakes

  • Missing a variant in match:
let name = match d {
    Direction::North => "north",
    Direction::South => "south",
    Direction::East => "east",
};
error[E0004]: non-exhaustive patterns: `Direction::West` not covered

Add the missing arm, or a _ => arm if the remaining cases really should share one behavior.

  • Using an Option<T> as if it were a T. let total = maybe + 1; where maybe is an Option<i32>:
error[E0369]: cannot add `{integer}` to `Option<i32>`

Get the value out first with match, if let, or unwrap_or.

  • Reaching for .unwrap() on an Option. It panics on None; prefer match, if let, or unwrap_or unless you're certain a value is present.
  • Forgetting the enum name: write Direction::North, not North.

Practice

Define an enum Coin with the variants Penny, Nickel, Dime, and Quarter, and a function value_in_cents(coin: &Coin) -> u32 that uses match to return 1, 5, 10, or 25. Add up one of each coin and print Total: 41 cents.

Show solution
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: &Coin) -> u32 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {
    let coins = [Coin::Penny, Coin::Nickel, Coin::Dime, Coin::Quarter];
    let mut total = 0;
    for coin in &coins {
        total += value_in_cents(coin);
    }
    println!("Total: {total} cents");
}