1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rust】ADT・直和・直積ってなに?「ありえない状態」を型で防ぐ話

Posted at

Rustを勉強していると「ADT」「直和型」「直積型」という言葉に出会います。

最初は「なんか数学っぽくて難しそう...」と思うかもしれませんが、実はシンプルな概念です。そして、Rustの強力な型システムの根幹を支えるめちゃくちゃ大事な考え方でもあります。

この記事では、これらの概念を具体例とともに解説します。

直積型(Product Type)= 「AかつB」

「複数の値を全部持つ」 型です。

Rustでの例

// struct は直積型
struct Person {
    name: String,   // かつ
    age: u32,       // かつ
    active: bool,
}

// タプルも直積型
type Point = (i32, i32);  // i32 かつ i32

なぜ「積」なの?

取りうる値の数が、各フィールドの値の数の 掛け算 になるからです。

// (bool, bool) の場合
// bool = 2通り(true, false)
// 2 × 2 = 4通り

let patterns: [(bool, bool); 4] = [
    (true, true),
    (true, false),
    (false, true),
    (false, false),
];

直和型(Sum Type)= 「AまたはB」

「複数の選択肢のうち、どれか一つだけを持つ」 型です。

Rustでの例

// enum は直和型
enum Result<T, E> {
    Ok(T),     // または
    Err(E),
}

enum Option<T> {
    Some(T),   // または
    None,
}

enum Shape {
    Circle(f64),              // または
    Rectangle(f64, f64),      // または
    Triangle(f64, f64, f64),
}

なぜ「和」なの?

取りうる値の数が、各バリアントの値の数の 足し算 になるからです。

// Option<bool> の場合
// Some(true), Some(false), None
// 2 + 1 = 3通り

ADT(代数的データ型)= 直積 + 直和

ADT(Algebraic Data Type) は、直積と直和を組み合わせて作る型の総称です。

「代数的」と呼ばれるのは、型を足し算(和)と掛け算(積)で計算できるからです。

// 直和の中に直積がある例
enum Message {
    Quit,                        // 値なし
    Move { x: i32, y: i32 },     // 直積(構造体)
    Write(String),               // 単一の値
    ChangeColor(u8, u8, u8),     // 直積(タプル)
}

直和型の真価:「ありえない状態」を型で防ぐ

ここからが本題です。直和型の最大のメリットは、「ありえない状態を型レベルで表現不可能にする」 ことです。

悪い例:直積だけで状態を表現

struct Connection {
    is_connected: bool,
    socket: Option<TcpStream>,
    error: Option<String>,
}

この設計には問題があります。

// ありえないはずの状態が表現できてしまう
let invalid = Connection {
    is_connected: false,    // 切断中なのに...
    socket: Some(stream),   // ソケットがある?
    error: None,
};

// あるいは
let also_invalid = Connection {
    is_connected: true,     // 接続中なのに...
    socket: None,           // ソケットがない?
    error: Some("...".into()),
};

フィールドの組み合わせによって、論理的にありえない状態が生まれてしまいます。

良い例:直和型で状態を表現

enum Connection {
    Disconnected,
    Connecting,
    Connected(TcpStream),
    Failed(String),
}

この設計なら、各状態で必要なデータだけを持つ ことが保証されます。

fn handle_connection(conn: Connection) {
    match conn {
        Connection::Disconnected => {
            // ソケットがないことが型で保証されている
            println!("接続されていません");
        }
        Connection::Connecting => {
            println!("接続中...");
        }
        Connection::Connected(stream) => {
            // ソケットが必ず存在することが型で保証されている
            send_data(&stream);
        }
        Connection::Failed(error) => {
            // エラー情報が必ず存在することが型で保証されている
            eprintln!("接続失敗: {}", error);
        }
    }
}

もう一つの例:ユーザーの認証状態

悪い例

struct User {
    is_logged_in: bool,
    user_id: Option<u64>,
    guest_session_id: Option<String>,
}

「ログイン済みなのにuser_idがNone」「ゲストなのにuser_idがある」といった矛盾が起こりえます。

良い例

enum User {
    Guest { session_id: String },
    LoggedIn { user_id: u64, username: String },
    Admin { user_id: u64, permissions: Vec<String> },
}

各状態に必要なデータが明確で、矛盾が起きません。

Option と Result が強力な理由

Rustの Option<T>Result<T, E> が強力なのは、まさにこの直和型の恩恵です。

// Option<T> は「値があるかないか」を直和で表現
enum Option<T> {
    Some(T),
    None,
}

// Result<T, E> は「成功か失敗か」を直和で表現
enum Result<T, E> {
    Ok(T),
    Err(E),
}

他の言語のように null や例外に頼らず、型システムで「値がないかもしれない」「失敗するかもしれない」を表現 できます。

そして match で全パターンを網羅しないとコンパイルエラーになるため、処理漏れを防げます

まとめ

概念 意味 Rustでの実現 値の数
直積型 AかつB(全部持つ) struct, タプル 掛け算
直和型 AまたはB(一つだけ) enum 足し算
ADT 直積と直和の組み合わせ struct + enum -

直和型を使いこなすと、「ありえない状態をコンパイル時に弾く」 設計ができるようになります。

これはRustの大きな強みであり、「コンパイルが通れば安心」と言われる所以でもあります。

ぜひ自分のコードでも「この bool フラグ、enum にできないかな?」と考えてみてください!

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?