Enums and Pattern Matching

Struct Variants

A struct variant is an enum variant that has named fields enclosed in curly braces VariantName { field1: T1, field2: T2 }, combining the multi-case sum-type nature of an enum with the self-documenting field names of a named-field struct.

Declaring and Constructing Struct Variants

#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    Triangle { base: f64, height: f64 },
}

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

fn main() {
    let shapes = [
        Shape::Circle { radius: 2.0 },
        Shape::Rectangle { width: 4.0, height: 5.0 },
        Shape::Triangle { base: 6.0, height: 3.0 },
    ];

    for s in &shapes {
        println!("{s:?} -> area = {:.2}", s.area());
    }
}
Circle { radius: 2.0 } -> area = 12.57
Rectangle { width: 4.0, height: 5.0 } -> area = 20.00
Triangle { base: 6.0, height: 3.0 } -> area = 9.00

Field Init Shorthand and Pattern Matching on Struct Variants

Struct variants support the exact same field init shorthand at construction time and shorthand / renamed / .. rest patterns during pattern matching as regular named-field structs:

enum AuditLog {
    Login { user: String, ip: String, success: bool },
}

fn summarize_log(entry: &AuditLog) {
    match entry {
        // Match `success: false`, bind `user`, and ignore `ip` with `..`:
        AuditLog::Login { user, success: false, .. } => {
            println!("ALERT: Failed login attempt for '{user}'");
        }
        // Rename `ip` to `source_ip` during destructuring:
        AuditLog::Login { user, ip: source_ip, success: true } => {
            println!("OK: '{user}' logged in from {source_ip}");
        }
    }
}

fn main() {
    let user = String::from("admin");
    let ip = String::from("10.0.0.1");

    // Field init shorthand on a struct variant:
    let ev1 = AuditLog::Login { user, ip, success: true };
    let ev2 = AuditLog::Login {
        user: String::from("guest"),
        ip: String::from("192.168.1.9"),
        success: false,
    };

    summarize_log(&ev1);
    summarize_log(&ev2);
}
OK: 'admin' logged in from 10.0.0.1
ALERT: Failed login attempt for 'guest'

When to Use a Struct Variant vs Tuple Variant

Use Case Preferred Variant Style
Single unambiguous value (Some(T), Text(String)) Tuple Variant
2+ fields of the same type whose order could be confused (width vs height, src vs dst) Struct Variant
Variant payload needs its own methods or is passed to helper functions by itself 1-element Tuple Variant wrapping a standalone struct (Resize(ResizeParams))