逆引き Rust 列挙体

# はじめに

* Rust の列挙体はデータを持てるというのがユニークですし、パターンマッチングと組み合わせるとパワフルでとても便利です。
* ここではそんな列挙体の使い方を逆引き形式でまとめてみました。

# 単純な列挙体を宣言する・生成する・評価する

```rs
#[allow(dead_code)]
fn main() {

    // 単純な enum の定義
    enum E1 {
        A,
        B,
        C,
    }

    // enum の値を生成
    let e1 = E1::B;

    // match 式で enum を評価する
    let result = match e1 {
        E1::A => 1,
        E1::B => 2,
        E1::C => 3,
    };
    assert_eq!(result, 2);

    // if let 式で enum を評価する
    let e1 = E1::C;
    let result = if let E1::C = e1 {
        true
    } else {
        false
    };
    assert!(result);
}
```

* [Playground で試す](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=37b2e0dd2cef53c5bca6831c27ed7891)



# データを持つ列挙体を宣言する・生成する・評価する

```rs
#[allow(dead_code)]
fn main() {

    // 複雑な enum の定義
    enum E2 {
        // データを持たないヴァリアント
        N,
        // 名前なしのデータを持つヴァリアント
        T(i32),
        // 名前付きデータを持つヴァリアント
        S{x: i32, y:i32},
    }

    // 名前なしデータを持つヴァリアントの初期化
    let e2 = E2::T(-1);

    // match 式で名前なしデータを持つヴァリアントを評価する
    let result = match e2 {
        E2::N => 1,
        E2::T(n) => n * 2,
        E2::S {x: _, y: _} => 3,
    };
    assert_eq!(result, -2);

    // if let 式で名前なしデータを持つヴァリアントを評価する
    let e2 = E2::T(10);
    let result = if let E2::T(n) = e2 { n } else { 0 };
    assert_eq!(result, 10);
}
```

* [Playground で試す](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fef84163aeaedb189ed45c2728f37ee7)


# 列挙体の比較をする

* `PartialOrd` と `PartialEq` を使うとヴァリアント同士での比較ができるようになります。
* データ付きヴァリアント同士が持っているデータに関係なく同じであるかどうか判定したい場合は `std::mem::discriminant` を使います。

```rs
#[allow(dead_code)]
fn main() {

    // PartialOrd, PartialEq で enum のヴァリアント同士を比較できるようになる
    #[derive(PartialOrd, PartialEq, Debug)]
    enum E3 {
        N1,
        N2,
        T1(i32),
        T2(i32),
        S1{x: i32, y: i32},
        S2{x: i32, y: i32},
    }

    // 同じ値かどうか比較できる
    assert_eq!(E3::N1, E3::N1);
    assert_ne!(E3::N1, E3::N2);

    // enum のヴァリアント同士での大小を比較できる
    assert!(E3::N1 < E3::N2);

    // 名前なしデータを持つヴァリアント同士の比較
    assert_eq!(E3::T1(10), E3::T1(10), "同じヴァリアントで同じデータなら同じ値");
    assert_ne!(E3::T1(10), E3::T1(20), "同じヴァリアントで違うデータなら違う値");
    assert_ne!(E3::T1(10), E3::T2(10), "違うヴァリアントで同じデータなら違う値");

    // 名前なしデータを持つヴァリアント同士の比較はデータに関係なくヴァリアントの宣言順
    assert!(E3::T1(1) < E3::T2(1));
    assert!(E3::T1(2) < E3::T2(1));
    assert!(E3::T1(1) < E3::T2(2));

    // 名前付きデータを持つヴァリアント同士の比較
    assert_eq!(E3::S1{x:10, y:20}, E3::S1{x:10, y:20}, "同じヴァリアントで同じデータなら同じ値");
    assert_ne!(E3::S1{x:10, y:20}, E3::S1{x:10, y:30}, "同じヴァリアントで違うデータなら違う値");
    assert_ne!(E3::S1{x:10, y:20}, E3::S2{x:10, y:20}, "違うヴァリアントで同じデータなら違う値");

    // 異なる形式のヴァリアント同士でも宣言順で大小を比較できる
    assert!(E3::N1 < E3::T1(1));

    // データを持つヴァリアントでデータを無視して同じヴァリアントかどうかを判定する
    use std::mem;
    assert_eq!(mem::discriminant(&E3::T1(100)), mem::discriminant(&E3::T1(200)), "同じヴァリアント違うデータで同値判定");
    assert_ne!(mem::discriminant(&E3::T1(100)), mem::discriminant(&E3::T2(100)), "違うヴァリアント同じデータで異値判定");
}
```

* [Playground で試す](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f2b4c3e642fe8ba225311793b83bfa59)


# 列挙体から文字列へ

* enum で `std::fmt::Display` を実装すると文字列への変換ができます

```rs
#[allow(dead_code)]
fn main() {

    enum E4 {
        A,
        B(i32),
        C{n: i32, m: i32}
    }

    // std::fmt::Display を実装すると文字列表現が定義できる
    use std::fmt;
    impl fmt::Display for E4 {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match *self {
                E4::A => write!(f, "[E4::A]"),
                E4::B(n) => write!(f, "[E4::B({})]", n),
                E4::C{n: nn, m: mm} => write!(f, "[E4::C{{n:{},m:{}}}]", nn, mm),
            }
        }
    }

    assert_eq!(format!("{}", E4::A), "[E4::A]", "format! で意図したとおりに出力できる");
    assert_eq!(E4::B(10).to_string(), "[E4::B(10)]", "to_string() でも同様の文字列が取得できる");
    assert_eq!(E4::C{n: 1, m: 2}.to_string(), "[E4::C{n:1,m:2}]", "データも正常に反映される");
}
```

* [Playground で試す](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f93bd6c6a73714aa85f89062e0f3204b)


# 文字列から列挙体へ

* enum で `std::str::FromStr` を実装すると文字列からの変換ができます

```rs
#[allow(dead_code)]
fn main() {

    #[derive(PartialEq, Debug)]
    enum E5 {
        A,
        B,
        C
    }

    use std::str::FromStr;
    impl FromStr for E5 {
        type Err = ();
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            match s {
                "A" => Ok(E5::A),
                "B" => Ok(E5::B),
                "C" => Ok(E5::C),
                _ => Err(())
            }
        }
    }

    assert_eq!(E5::from_str("A"), Ok(E5::A), "from_str で文字列から enum に");
    assert_eq!(E5::from_str("B"), Ok(E5::B), "from_str で文字列から enum に");
    assert_eq!("C".parse::<E5>(), Ok(E5::C), "from_str が定義されていると parse も使える");
}
```

* [Playground で試す](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6cc86360d3772130c330b39aa601f58c)