rust で数値からenumに変換する

rustのenumを定数を列挙するためだけに使いたいということもありますね?(ないですか?)

そういう時、数値からenumに変換したいということがありますね?(ないですか?)

rustのenumはこんなふうに定義します。 rustのenumはだいぶ高機能なのでいろんなことができますが、今回は定数の列挙に使いたいという想定ですね。

enum TrafficLight{
    Blue,
    Yellow,
    Red,
}

このように定義するとデフォルトで0から数値がふられるので enum から数値に 変換することはできます。

fn main() {
    println!("{:?}  = {}", TrafficLight::Blue, TrafficLight::Blue as i32);
    println!("{:?}  = {}", TrafficLight::Yellow, TrafficLight::Yellow as i32);
    println!("{:?}  = {}", TrafficLight::Red, TrafficLight::Red as i32);
}

結果

$ cargo run
Blue    = 0
Yellow  = 1
Red     = 2

こうなります。

逆はできません。

$ cargo build
src/main.rs:12:32: 12:49 error: non-scalar cast: `i32` as `TrafficLight`
src/main.rs:12     let light : TrafficLight = 0 as TrafficLight;
                                              ^~~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `light`.

To learn more, run the command again with --verbose.

こうなります。

調べてみると以下がでてきますね。

stackoverflow.com

書いてある通りに std::num::FromPrimitive は削除されてしまいましたが、今は http://doc.rust-lang.org/num/num/index.html に同様な機能を持つものが入っています。

crateに頼ることになるのは遺憾ですが公式が提供してる(?)ものなのでまぁいいとしましょう。

これを使って FromPrimitive という trait を実装すると

from_i64(n: i64)
from_u64(n: u64)
from_isize(n: isize)
from_i8(n: i8)
from_i16(n: i16)
from_i32(n: i32)
from_usize(n: usize)
from_u8(n: u8)
from_u16(n: u16)
from_u32(n: u32)
from_f32(n: f32)
from_f64(n: f64)

これらが使えるようになるので、基本の数値型ならどれからでも変換できるようになります。

実装する必要があるのは、 from_i64from_u64 の2つです。

以下使い方

Cargo.toml に依存を追加しましょう

[dependencies]
num = "*"

で実装してきます

extern crate num;
use num::traits::FromPrimitive;

impl FromPrimitive for TrafficLight {
    fn from_i64(n: i64) -> Option<TrafficLight> {
        match n {
            0 => Some(TrafficLight::Blue),
            1 => Some(TrafficLight::Yellow),
            2 => Some(TrafficLight::Red),
            _ => None,
        }
    }
    fn from_u64(n: u64) -> Option<TrafficLight> {
        match n {
            0 => Some(TrafficLight::Blue),
            1 => Some(TrafficLight::Yellow),
            2 => Some(TrafficLight::Red),
            _ => None,
        }
    }
}

こうです。

このmatchあたりがめんどくさいのでなんとかしたいですね。いい方法があるといいんですが。

ここに数値を書かないといけないとなると網羅してるかもよくわからないし変更や追加に弱いしこれはなんとかしたいです。

(これのいい方法が知りたくてこんな記事をだらだら書いてると言っても過言ではありません。誰か教えてください。)

    let blue = TrafficLight::from_i64(0 as i64).unwrap();
    let yellow = TrafficLight::from_u8(1 as u8).unwrap();
    let red = TrafficLight::from_usize(2 as usize).unwrap();
    println!("{:?}", blue);
    println!("{:?}", yellow);
    println!("{:?}", red);

結果

$ cargo run
Blue
Yellow
Red

できましたね