Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

Rust辞典

  1. トップページ
  2. Rust辞典
  3. enum を使ったデータモデリング

enum を使ったデータモデリング

Rustのenumは値の種類によって異なるデータを持てるため、複雑なドメインモデルを安全に表現できます。状態機械(state machine)やコマンドパターンなど実践的な設計に活用されます。

構文
// 支払い方法を表すenumです。
#[derive(Debug)]
enum Payment {
    Cash(f64),
    CreditCard { number: String, amount: f64 },
    CryptoCurrency { coin: String, wallet: String, amount: f64 },
}

// 状態機械パターン: 接続状態を表します。
#[derive(Debug)]
enum ConnectionState {
    Disconnected,
    Connecting { host: String, port: u16 },
    Connected { session_id: u64, latency_ms: u32 },
    Error(String),
}
活用パターン一覧
パターン概要
状態機械各バリアントがシステムの状態を表します。不正な状態遷移をコンパイル時に防げます。
コマンドパターン各バリアントが1つの操作(コマンド)を表します。
AST(抽象構文木)再帰的なenumでプログラムの構造を表現します(Box<T>でラップ必要)。
エラー型失敗の種類をバリアントで表現します。
Option<T> の内部Some(T) / None で「値があるかどうか」を表します。
Result<T, E> の内部Ok(T) / Err(E) で「成功か失敗か」を表します。
サンプルコード
// タスクの状態を表すenumです。
#[derive(Debug)]
enum TaskStatus {
    Todo,
    InProgress { started_at: String, assignee: String },
    Done { completed_at: String },
    Blocked(String),  // ブロッカーの理由
}

#[derive(Debug)]
struct Task {
    id: u32,
    title: String,
    status: TaskStatus,
}

impl Task {
    fn is_done(&self) -> bool {
        matches!(self.status, TaskStatus::Done { .. })
    }

    fn summary(&self) -> String {
        match &self.status {
            TaskStatus::Todo =>
                format!("[{}] {} - 未着手", self.id, self.title),
            TaskStatus::InProgress { assignee, .. } =>
                format!("[{}] {} - {} が対応中", self.id, self.title, assignee),
            TaskStatus::Done { completed_at } =>
                format!("[{}] {} - {} 完了", self.id, self.title, completed_at),
            TaskStatus::Blocked(reason) =>
                format!("[{}] {} - ブロック中: {}", self.id, self.title, reason),
        }
    }
}

// JSON的なデータを表すenumです(再帰的なenum)。
#[derive(Debug)]
enum JsonValue {
    Null,
    Bool(bool),
    Number(f64),
    Str(String),
    Array(Vec<JsonValue>),
}

impl JsonValue {
    fn to_string(&self) -> String {
        match self {
            JsonValue::Null => "null".to_string(),
            JsonValue::Bool(b) => b.to_string(),
            JsonValue::Number(n) => n.to_string(),
            JsonValue::Str(s) => format!("\"{}\"", s),
            JsonValue::Array(arr) => {
                let items: Vec<String> = arr.iter().map(|v| v.to_string()).collect();
                format!("[{}]", items.join(", "))
            }
        }
    }
}

fn main() {
    let tasks = vec![
        Task {
            id: 1,
            title: String::from("要件定義"),
            status: TaskStatus::Done {
                completed_at: String::from("2024-01-10"),
            },
        },
        Task {
            id: 2,
            title: String::from("設計"),
            status: TaskStatus::InProgress {
                started_at: String::from("2024-01-11"),
                assignee: String::from("Alice"),
            },
        },
        Task {
            id: 3,
            title: String::from("実装"),
            status: TaskStatus::Blocked(String::from("設計待ち")),
        },
        Task {
            id: 4,
            title: String::from("テスト"),
            status: TaskStatus::Todo,
        },
    ];

    for task in &tasks {
        println!("{}", task.summary());
    }

    let done_count = tasks.iter().filter(|t| t.is_done()).count();
    println!("完了タスク: {}/{}", done_count, tasks.len());

    // JsonValue の使用例です。
    let data = JsonValue::Array(vec![
        JsonValue::Number(1.0),
        JsonValue::Str(String::from("hello")),
        JsonValue::Bool(true),
        JsonValue::Null,
    ]);
    println!("JSON: {}", data.to_string());
}
概要

Rustのenumを使った設計は「不正な状態を型で表現できない(Making illegal states unrepresentable)」というコンセプトを実現します。例えば『Connected』バリアントにしか『session_id』がないため、接続前に『session_id』にアクセスするコードはコンパイルエラーになります。

『matches!()』マクロはmatch式を簡潔に書けるユーティリティです。特定のバリアントかどうかをboolで確認するときに使います。

enumの基本定義は『enum / バリアント定義』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。