Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

Rust Dictionary

  1. Home
  2. Rust Dictionary
  3. Data Modeling with enum

Data Modeling with enum

Since: Rust 1.0(2015)

Rust enums can hold different data depending on the variant, making it possible to safely model complex domains. They are commonly used in practical patterns such as state machines and the command pattern.

Syntax

#[derive(Debug)]
enum Payment {
    Cash(f64),
    CreditCard { number: String, amount: f64 },
    CryptoCurrency { coin: String, wallet: String, amount: f64 },
}

// State machine pattern: represents a connection state.
#[derive(Debug)]
enum ConnectionState {
    Disconnected,
    Connecting { host: String, port: u16 },
    Connected { session_id: u64, latency_ms: u32 },
    Error(String),
}

Common Usage Patterns

PatternDescription
State machineEach variant represents a state of the system. Invalid state transitions can be prevented at compile time.
Command patternEach variant represents a single operation (command).
AST (Abstract Syntax Tree)Recursive enums represent the structure of a program (requires wrapping with Box<T>).
Error typeEach variant represents a distinct kind of failure.
Inside Option<T>Some(T) / None represents whether a value is present.
Inside Result<T, E>Ok(T) / Err(E) represents success or failure.

Sample Code

Pattern 1: sample entry modeling with Category

sample_enum_data_modeling.rs
#[derive(Debug)]
enum Category {
    TypeA { label: String, origin: String },
    TypeB { base: String },
    Special { affiliation: String, power: f64 },
}

#[derive(Debug)]
struct Entry {
    id: u32,
    name: String,
    category: Category,
}

impl Entry {
    fn is_special(&self) -> bool {
        matches!(self.category, Category::Special { .. })
    }

    fn profile(&self) -> String {
        match &self.category {
            Category::TypeA { label, .. } =>
                format!("[{}] {} - assigned to {}", self.id, self.name, label),
            Category::TypeB { base } =>
                format!("[{}] {} - from {}", self.id, self.name, base),
            Category::Special { affiliation, .. } =>
                format!("[{}] {} - affiliated with {}", self.id, self.name, affiliation),
        }
    }
}

#[derive(Debug)]
enum MetaData {
    Title(String),
    WithValue { title: String, value: u32 },
    Collection(Vec<MetaData>),
}

impl MetaData {
    fn describe(&self) -> String {
        match self {
            MetaData::Title(t) => t.clone(),
            MetaData::WithValue { title, value } => format!("{} ({})", title, value),
            MetaData::Collection(list) => {
                let titles: Vec<String> = list.iter().map(|b| b.describe()).collect();
                format!("[{}]", titles.join(", "))
            }
        }
    }
}

fn main() {
    let entries = vec![
        Entry {
            id: 1,
            name: String::from("user_a"),
            category: Category::TypeA {
                label: String::from("group_a"),
                origin: String::from("group_a"),
            },
        },
        Entry {
            id: 2,
            name: String::from("user_c"),
            category: Category::TypeA {
                label: String::from("group_b"),
                origin: String::from("group_b"),
            },
        },
        Entry {
            id: 3,
            name: String::from("user_e"),
            category: Category::TypeB {
                base: String::from("org_a"),
            },
        },
        Entry {
            id: 4,
            name: String::from("user_b"),
            category: Category::Special {
                affiliation: String::from("org_a"),
                power: 0.95,
            },
        },
    ];

    for entry in &entries {
        println!("{}", entry.profile());
    }

    let special_count = entries.iter().filter(|f| f.is_special()).count();
    println!("Special: {}/{}", special_count, entries.len());

    let collection = MetaData::Collection(vec![
        MetaData::Title(String::from("item_a")),
        MetaData::WithValue { title: String::from("item_b"), value: 138 },
        MetaData::Title(String::from("item_c")),
    ]);
    println!("Meta: {}", collection.describe());
}

The following is an example:

rustc enum_data_modeling.rs
./enum_data_modeling
[1] user_a - assigned to group_a
[2] user_c - assigned to group_b
[3] user_e - from org_a
[4] user_b - affiliated with org_a
Special: 1/4
Meta: [item_a, item_b (138), item_c]

Pattern 2: State machine pattern

Each variant of the enum represents a state of the system. Invalid state transitions can be prevented at compile time. The following example models task phases as a state machine.

state_machine.rs
#[derive(Debug)]
enum TaskPhase {
    Waiting,
    Assigning { assignee1: String, assignee2: String },
    Running { step: u32, time_left: u32 },
    Done { winner: String, steps_completed: u32 },
}

impl TaskPhase {
    fn next(self) -> TaskPhase {
        match self {
            TaskPhase::Waiting => TaskPhase::Assigning {
                assignee1: String::from("user_a"),
                assignee2: String::from("user_b"),
            },
            TaskPhase::Assigning { assignee1, .. } => TaskPhase::Running {
                step: 1,
                time_left: 99,
            },
            TaskPhase::Running { step, .. } => TaskPhase::Done {
                winner: String::from("user_a"),
                steps_completed: step,
            },
            TaskPhase::Done { .. } => TaskPhase::Waiting,
        }
    }

    fn describe(&self) -> String {
        match self {
            TaskPhase::Waiting => String::from("Waiting"),
            TaskPhase::Assigning { assignee1, assignee2 } =>
                format!("Assigning: {} vs {}", assignee1, assignee2),
            TaskPhase::Running { step, time_left } =>
                format!("Step {} - {} seconds left", step, time_left),
            TaskPhase::Done { winner, steps_completed } =>
                format!("Done: {} ({} steps)", winner, steps_completed),
        }
    }
}

fn main() {
    let mut phase = TaskPhase::Waiting;
    for _ in 0..4 {
        println!("{}", phase.describe());
        phase = phase.next();
    }
}

Compile with the following command:

rustc state_machine.rs
./state_machine
Waiting
Assigning: user_a vs user_b
Step 1 - 99 seconds left
Done: user_a (1 steps)

Pattern 3: Command pattern (each variant represents an operation)

Each enum variant acts as a command. The type system represents the kind of operation, and match dispatches execution safely.

command_pattern.rs
#[derive(Debug)]
enum Command {
    Move { dx: i32, dy: i32 },
    Execute { action_name: String, cost: u32 },
    Wait,
    UseSpecial { name: String, cost: u32 },
}

struct TaskState {
    worker_name: String,
    hp: i32,
    meter: u32,
}

impl TaskState {
    fn execute(&mut self, cmd: &Command) {
        match cmd {
            Command::Move { dx, dy } =>
                println!("{} moves to ({}, {})", self.worker_name, dx, dy),
            Command::Execute { action_name, cost } =>
                println!("{} executes '{}' (cost: {})", self.worker_name, action_name, cost),
            Command::Wait =>
                println!("{} waits", self.worker_name),
            Command::UseSpecial { name, cost } => {
                if self.meter >= *cost {
                    self.meter -= cost;
                    println!("{} uses '{}' (meter: {})", self.worker_name, name, self.meter);
                } else {
                    println!("Not enough meter");
                }
            }
        }
    }
}

fn main() {
    let mut state = TaskState {
        worker_name: String::from("user_a"),
        hp: 1000,
        meter: 100,
    };

    let commands = vec![
        Command::Move { dx: 2, dy: 0 },
        Command::Execute { action_name: String::from("action_a"), cost: 120 },
        Command::Wait,
        Command::UseSpecial { name: String::from("special_a"), cost: 50 },
        Command::UseSpecial { name: String::from("special_b"), cost: 100 },
    ];

    for cmd in &commands {
        state.execute(cmd);
    }
}

Compile with the following command:

rustc command_pattern.rs
./command_pattern
user_a moves to (2, 0)
user_a executes 'action_a' (cost: 120)
user_a waits
user_a uses 'special_a' (meter: 50)
Not enough meter

Overview

Designing with Rust enums realizes the concept of "making illegal states unrepresentable." For example, because power only exists in the Special variant, any code that tries to access power on a non-Special entry will result in a compile error.

The matches!() macro checks whether a value matches a specific pattern and returns a bool. Also, the .. in the patterns above is a shorthand that ignores the remaining fields.

For the basics of defining enums, see enum / variant definition.

Common Mistakes

Mistake 1: Non-exhaustive match causes a compile error

A Rust match expression must cover all variants. Leaving even one variant uncovered causes a compile error.

ng_match_exhaustive.rs
#[derive(Debug)]
enum Category {
    TypeA,
    TypeB,
    Special,
}

fn describe(cat: &Category) -> &str {
    match cat {
        Category::TypeA => "Type A",
        Category::TypeB => "Type B",
    }
}

Compile with the following command:

rustc ng_match_exhaustive.rs
error[E0004]: non-exhaustive patterns: `Category::Special` not covered

Cover all variants, or use a wildcard pattern _.

ok_match_exhaustive.rs
#[derive(Debug)]
enum Category {
    TypeA,
    TypeB,
    Special,
}

fn describe(cat: &Category) -> &str {
    match cat {
        Category::TypeA => "Type A",
        Category::TypeB => "Type B",
        Category::Special => "Special",
    }
}

fn main() {
    let categories = vec![
        Category::TypeA,
        Category::TypeB,
        Category::Special,
    ];
    for c in &categories {
        println!("{:?}: {}", c, describe(c));
    }
}

Compile with the following command:

rustc ok_match_exhaustive.rs
./ok_match_exhaustive
TypeA: Type A
TypeB: Type B
Special: Special

Mistake 2: Syntax error when pattern-matching a data-bearing variant

When extracting data from a variant in a match expression, tuple variants require parentheses and struct variants require curly braces. Mixing them up causes a compile error.

ng_pattern_syntax.rs
#[derive(Debug)]
enum Command {
    Attack(String, u32),
    Move { dx: i32, dy: i32 },
}

fn execute(cmd: &Command) {
    match cmd {
        Command::Attack { name, damage } => println!("{} ({})", name, damage),
        Command::Move(dx, dy) => println!("move {} {}", dx, dy),
    }
}

Compile with the following command:

rustc ng_pattern_syntax.rs
error[E0769]: `Attack` is a tuple variant, not a struct variant
  --> ng_pattern_syntax.rs:10:9
   |
10 |         Command::Attack { name, damage } => println!("{} ({})", name, damage),
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0532]: expected tuple struct or tuple variant, found struct variant `Command::Move`
  --> ng_pattern_syntax.rs:11:9
   |
11 |         Command::Move(dx, dy) => println!("move {} {}", dx, dy),
   |         ^^^^^^^^^^^^^^^^^^^^^

Use parentheses for tuple variants and curly braces for struct variants.

ok_pattern_syntax.rs
#[derive(Debug)]
enum Command {
    Attack(String, u32),
    Move { dx: i32, dy: i32 },
}

fn execute(cmd: &Command) {
    match cmd {
        Command::Attack(name, damage) => println!("{} ({})", name, damage),
        Command::Move { dx, dy } => println!("move {} {}", dx, dy),
    }
}

fn main() {
    let commands = vec![
        Command::Attack(String::from("action_a"), 120),
        Command::Move { dx: 2, dy: 0 },
    ];
    for cmd in &commands {
        execute(cmd);
    }
}

Compile with the following command:

rustc ok_pattern_syntax.rs
./ok_pattern_syntax
action_a (120)
move 2 0

If you find any errors or copyright issues, please .