列挙型

Rustの enum は、いくつかのヴァリアントのうちからどれか一つをとるデータを表す型です。

fn main() { enum Message { Quit, ChangeColor(i32, i32, i32), Move { x: i32, y: i32 }, Write(String), } }
enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

各ヴァリアントは、自身に関連するデータを持つこともできます。 ヴァリアントの定義のための構文は、構造体を定義するのに使われる構文と似ており、 (unit-like構造体のような)データを持たないヴァリアント、名前付きデータを持つヴァリアント、(タプル構造体のような)名前なしデータを持つヴァリアントがありえます。 しかし、別々に構造体を定義する場合とは異なり、 enum は一つの型です。 列挙型の値はどのヴァリアントにもマッチしうるのです。 このことから、列挙型は「直和型」(sum type) と呼ばれることもあります。 列挙型としてとりうる値の集合は、各ヴァリアントとしてとりうる値の集合の和であるためです。

各ヴァリアントの名前を使うためには、 :: 構文を使います。 すなわち、ヴァリアントの名前は enum 自体の名前によってスコープ化されています。 これにより、以下のどちらもうまく動きます。

fn main() { enum Message { Move { x: i32, y: i32 }, } let x: Message = Message::Move { x: 3, y: 4 }; enum BoardGameTurn { Move { squares: i32 }, Pass, } let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 }; }
let x: Message = Message::Move { x: 3, y: 4 };

enum BoardGameTurn {
    Move { squares: i32 },
    Pass,
}

let y: BoardGameTurn = BoardGameTurn::Move { squares: 1 };

どちらのヴァリアントも Move という名前ですが、列挙型の名前でスコープ化されているため、衝突することなく使うことができます。

列挙型の値は、ヴァリアントに関連するデータに加え、その値自身がどのヴァリアントであるかという情報を持っています。 これを「タグ付き共用体」(tagged union) ということもあります。 データが、それ自身がどの型なのかを示す「タグ」をもっているためです。 コンパイラはこの情報を用いて、列挙型内のデータへ安全にアクセスすることを強制します。 例えば、値をどれか一つのヴァリアントであるかのようにみなして、その中身を取り出すということはできません。

fn main() { fn process_color_change(msg: Message) { // let Message::ChangeColor(r, g, b) = msg; // compile-time error let Message::ChangeColor(r, g, b) = msg; // コンパイル時エラー } }
fn process_color_change(msg: Message) {
    let Message::ChangeColor(r, g, b) = msg; // コンパイル時エラー
}

こういった操作が許されないことで制限されているように感じられるかもしれませんが、この制限は克服できます。 それには二つの方法があります。 一つは等値性を自分で実装する方法、もう一つは次のセクションで学ぶ match 式でヴァリアントのパターンマッチを行う方法です。 等値性を実装する方法についてはまだ説明していませんが、 トレイト のセクションに書いてあります。

関数としてのコンストラクタ

列挙型のコンストラクタも、関数のように使うことができます。 例えばこうです。

fn main() { enum Message { Write(String), } let m = Message::Write("Hello, world".to_string()); }
let m = Message::Write("Hello, world".to_string());

これは、以下と同じです。

fn main() { enum Message { Write(String), } fn foo(x: String) -> Message { Message::Write(x) } let x = foo("Hello, world".to_string()); }
fn foo(x: String) -> Message {
    Message::Write(x)
}

let x = foo("Hello, world".to_string());

このことは今すぐ役立つことではないのですが、クロージャ のセクションでは関数を他の関数へ引数として渡す話をします。 例えば、これを イテレータ とあわせることで、 String のベクタから Message::Write のベクタへ変換することができます。

fn main() { enum Message { Write(String), } let v = vec!["Hello".to_string(), "World".to_string()]; let v1: Vec<Message> = v.into_iter().map(Message::Write).collect(); }

let v = vec!["Hello".to_string(), "World".to_string()];

let v1: Vec<Message> = v.into_iter().map(Message::Write).collect();