SyntaxStudy
Sign Up
Rust Custom Enums with Data and Methods
Rust Beginner 1 min read

Custom Enums with Data and Methods

Because each variant of a Rust enum can hold different data, enums are the natural choice for modelling messages, commands, state machines, and abstract syntax trees. A single enum type can replace what would require an inheritance hierarchy in an object-oriented language, but with the added guarantee that the compiler checks exhaustiveness every time you pattern-match on it. You can define methods on enums the same way as on structs — using `impl` blocks. Methods can match on `self` to provide variant-specific behaviour. Enums can also implement traits, enabling polymorphism without dynamic dispatch. Combined with `Box` when you need heterogeneous collections, Rust's enum-based approach covers the vast majority of use cases for type-based dispatch. Enums are frequently used to model the states of a protocol or UI component. Each variant represents a state, and the data carried by the variant is exactly what that state needs. Transitions between states are modelled as functions that consume an old state enum value and produce a new one, making illegal state transitions impossible to represent at the type level.
Example
#[derive(Debug, Clone)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColour(u8, u8, u8),
}

impl Message {
    fn describe(&self) -> String {
        match self {
            Message::Quit => "quit".to_string(),
            Message::Move { x, y } => format!("move to ({x}, {y})"),
            Message::Write(text) => format!("write: {text}"),
            Message::ChangeColour(r, g, b) => format!("colour: rgb({r},{g},{b})"),
        }
    }

    fn is_quit(&self) -> bool {
        matches!(self, Message::Quit)
    }
}

// State machine example
#[derive(Debug)]
enum TrafficLight {
    Red,
    Yellow,
    Green,
}

impl TrafficLight {
    fn next(self) -> Self {
        match self {
            TrafficLight::Red => TrafficLight::Green,
            TrafficLight::Green => TrafficLight::Yellow,
            TrafficLight::Yellow => TrafficLight::Red,
        }
    }

    fn duration_secs(&self) -> u32 {
        match self { TrafficLight::Red => 60, TrafficLight::Green => 45, TrafficLight::Yellow => 5 }
    }
}

fn main() {
    let messages = vec![
        Message::Move { x: 10, y: 20 },
        Message::Write(String::from("hello")),
        Message::ChangeColour(255, 0, 128),
        Message::Quit,
    ];
    for m in &messages {
        println!("{}", m.describe());
    }
    println!("last is quit: {}", messages.last().unwrap().is_quit());

    let mut light = TrafficLight::Red;
    for _ in 0..4 {
        println!("{:?} — {}s", light, light.duration_secs());
        light = light.next();
    }
}