Rustの列挙型enumの使い方まとめ

Rustの列挙型(enum)は奥が深いのでまとめてみました。

# 定数値として使う

まず、Enumはある特定の値の定数値として使うことが可能です。まとまりのある定数を定義するときに使えて便利です。

```rgb.rs
enum Color {
    Blue = 0x0000FF,
    Green = 0x00FF00,
    Red = 0xFF0000,
}

fn main() {
    let c = Color::Red;
    println!("{}", c as usize);
}
```

## 特定の値を指定しない時は自動的に値が割り振られる

特定の値を指定せずにenumを定義すると、自動的に数値が割り当てられます。asを使ってenum型をusizeに変換できます。

```colorcode.rs
enum Color {
    Black,
    Red,
    Green,
    Blue,
    White,
}

fn main() {
    // usizeの値に変換可能
    let code = Color::Blue as usize;
    println!("{}", code); // → 3
```


# 値を持つ列挙型

ただし、enumの型が値を保持するようにしていると簡単に数値変換することはできません。
enumが持つ値を取り出すには、match文を使います。

```rgb2.rs
enum Color {
    RGB(u8, u8, u8),
    RGBA(u8, u8, u8, u8),
}

fn main() {
    let c = Color::RGB(0, 0, 255);
    let code = match c {
        Color::RGB(r, g, b) => b as u32 + ((g as u32) << 8) + ((r as u32) << 16),
        Color::RGBA(r, g, b, _) => b as u32 + ((g as u32) << 8) + ((r as u32) << 16),
    };
    println!("{}", code);
}
```

# 値を持つenumを比較したい

値を持つ列挙型を使う場合、値を比較するのが大変です。この場合、Enumの型＋値が等価である必要があります。

```enum_compare.rs
// 比較のためには Debug と PartialEq を指定する必要がある
#[derive(Debug,PartialEq)]
enum Animal {
    Dog(String),
    Cat(String),
    Monkey(String),
}

fn main() {
    // 全く同じか調べる --- (*1)
    let taro1 = Animal::Dog(String::from("Taro"));
    let taro2 = Animal::Dog(String::from("Taro"));
    assert_eq!(taro1, taro2); // eq

    // 同じ値を持つが DogとCat なので異なる --- (*2)
    let taro3 = Animal::Cat(String::from("Taro"));
    let taro4 = Animal::Dog(String::from("Taro"));
    assert_ne!(taro3, taro4); // ne

    // 値を持つ型は usize に変換できない --- (*3)
    let taro5 = Animal::Monkey(String::from("Taro"));
    let taro6 = Animal::Monkey(String::from("Taro"));
    // assert_eq!(taro5 as usize, taro6 as usize); // エラー
}
```

上記(*1)のようにenum型＋値が等価であれば等しいと見なされます。
また(*2)のように値が同じでもenum型が異なれば等しくありません。
(*3)のコメントを外すと分かりますが、値を持つenum型はusizeにキャストできません。

## 値を持つenumを無理矢理に数値化する方法

せっかくenumなのに数値として比較できないと不便な場合もあります。その場合、面倒ですが、matchで分岐させて任意の数値を返すようなメソッドを定義します。

```enum_compare2.rs
enum Animal {
    Dog(String),
    Cat(String),
    Monkey(String),
}
impl Animal {
    // enum型を数値に変換するメソッドを定義する
    fn to_usize(&self) -> usize {
        match self {
            Animal::Dog(_) => 0,
            Animal::Cat(_) => 1,
            Animal::Monkey(_) => 2,
        }
    }
}

fn main() {
    let jiro1 = Animal::Dog(String::from("Jiro"));
    let jiro2 = Animal::Cat(String::from("Jiro"));
    assert_ne!(jiro1.to_usize(), jiro2.to_usize());
}
```

また、せっかくの列挙型を活かして、値を持たないenumを構造体(struct)の中に入れてしまう手もあります。列挙型の種類が多い場合、この方が便利でしょうか。

```enum_struct.rs
// 値を持たない列挙型を定義
enum AnimalKind {
    Dog, Cat, Monkey, Pig, Bird,
}
// 構造体を定義して、列挙型を要素に加える
struct Animal {
    kind: AnimalKind,
    name: String,
}

fn main() {
    let jiro1 = Animal{kind:AnimalKind::Dog, name:String::from("Jiro")};
    let jiro2 = Animal{kind:AnimalKind::Cat, name:String::from("Jiro")};
    assert_ne!(jiro1.kind, jiro2.kind); // 種類の比較が簡単！
}
```

## 所感

使い勝手で比較した印象としては、「値を持つ列挙型」と「値を持たない列挙型」は明確に区別して使うと良いでしょう。その上で、どの方法を採用すると効率が良いのか、よく考えて使い分ける必要があります。


# 参照

https://doc.rust-lang.org/rust-by-example/custom_types/enum.html

https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html

- (参考) [『Rustの書き方・作り方』](https://amzn.to/3mnx9qe)という本を書きました。