Enums and Pattern Matching

Defining Enums

An enum (short for enumeration, also called a sum type or tagged union in type theory) defines a custom data type by listing all of its possible variants. Whereas a struct groups multiple fields together so an instance holds all of them at once (an "AND" type), an enum represents a value that can be exactly one of several possible alternatives at any given moment (an "OR" type).

Struct ("AND" Product Type) vs Enum ("OR" Sum Type)

Struct (Product Type — holds ALL fields simultaneously):
  struct HttpResponse {
      status: u16,       AND
      body: String,
  }

Enum (Sum Type — holds EXACTLY ONE variant at a time):
  enum NetworkEvent {
      Connected,         OR
      Message(String),   OR
      Disconnected { code: u16, reason: String },
  }

Declaring and Instantiating an Enum

An enum is declared with the enum keyword followed by its name in UpperCamelCase and a brace-enclosed list of variants (also in UpperCamelCase). Variants are accessed using the namespace path operator :: (EnumName::VariantName):

#[derive(Debug, PartialEq)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

fn wait_time_seconds(light: &TrafficLight) -> u32 {
    match light {
        TrafficLight::Red => 45,
        TrafficLight::Yellow => 5,
        TrafficLight::Green => 0,
    }
}

fn main() {
    let current = TrafficLight::Red;
    let next = TrafficLight::Green;

    println!("Current light: {current:?} (wait {}s)", wait_time_seconds(&current));
    println!("Same state?    {}", current == next);
}
Current light: Red (wait 45s)
Same state?    false

Notice that all variants (TrafficLight::Red, TrafficLight::Yellow, TrafficLight::Green) have the exact same type: TrafficLight. A function accepting TrafficLight can receive any of its variants.

Mixing Variant Shapes in a Single Enum

Unlike C, C++, or Java enums—where variants are merely named integer constants—each variant of a Rust enum can optionally carry its own associated data of any type and shape:

Variant Kind Syntax Inside enum Mirrors Which struct Form? Construction Syntax
Unit variant Quit, Unit-like struct (struct Quit;) Command::Quit
Tuple variant Write(String), or Move(i32, i32), Tuple struct (struct Write(String);) Command::Write(text)
Struct variant Resize { width: u32, height: u32 }, Named-field struct (struct Resize { ... }) Command::Resize { width: 800, height: 600 }
#[derive(Debug)]
enum Command {
    Quit,                                // Unit variant (no payload)
    Echo(String),                        // Tuple variant (1 field)
    MoveTo(i32, i32),                    // Tuple variant (2 fields)
    Resize { width: u32, height: u32 },  // Struct variant (named fields)
}

fn main() {
    let queue = [
        Command::MoveTo(10, -20),
        Command::Resize { width: 1920, height: 1080 },
        Command::Echo(String::from("Hello, enums!")),
        Command::Quit,
    ];

    for cmd in &queue {
        println!("Queued command: {cmd:?}");
    }
}
Queued command: MoveTo(10, -20)
Queued command: Resize { width: 1920, height: 1080 }
Queued command: Echo("Hello, enums!")
Queued command: Quit

Defining Methods on Enums (impl)

Just like structs, enums can have methods and associated functions defined on them inside an impl block:

#[derive(Debug)]
enum PaymentMethod {
    Cash,
    Card { last_four: u16 },
    Voucher(u64),
}

impl PaymentMethod {
    fn summary(&self) -> String {
        match self {
            Self::Cash => String::from("Paid in cash"),
            Self::Card { last_four } => format!("Paid with card ending in {last_four:04}"),
            Self::Voucher(code) => format!("Redeemed voucher #{code}"),
        }
    }
}

fn main() {
    let payment = PaymentMethod::Card { last_four: 42 };
    println!("{}", payment.summary());
}
Paid with card ending in 0042

Inside impl PaymentMethod, Self::Cash is a convenient shorthand for PaymentMethod::Cash.

Bringing Enum Variants into Scope with use

If you match or construct an enum's variants repeatedly inside a function or module, you can bring its variants directly into local scope with use EnumName::*; (just as the standard prelude automatically imports Option::Some, Option::None, Result::Ok, and Result::Err):

#[derive(Debug)]
enum LogLevel {
    Info,
    Warn,
    Error,
}

fn main() {
    use LogLevel::*;

    let level = Warn; // Written directly without `LogLevel::` prefix
    println!("Active log level: {level:?}");
}
Active log level: Warn