dullwhaleのメモ帳

何度も同じことを調べなくてよいように...

Rustでenumと文字列の相互変換

Rustで文字列の定数を定義したくなることがある。 文字列定数の実現方法にはいくつかあるがmatch構文の恩恵を受けられるenumを使うと良い場合がある。 ところがRustのenumC言語のそれに近く、enum variantの値として整数リテラルを設定することはできるが、文字列リテラルを設定することはできない。 正確にはenumのvariantはコンパイル時に全て整数値へ置き換えられる? 指定しなかった場合はC言語と同様に0から採番される。 次のようには書けるが、

// これはできる
enum TCPFlag {
    FIN = 0x0001,
    SYN = 0x0002,
    RST = 0x0004,
    PSH = 0x0008,
    ACK = 0x0010,
    URG = 0x0020,
}

次のようには書けない。

// これはできない
enum ConfigKey {
    MaxMemory = "MaxMemory",
    MaxProcess = "MaxProcess",
    AllowEventuallyConsistency = "AllowEventuallyConsistency",
}

これを解決するためには各enum variantと文字列の相互変換を可能にすればよい。 このとき、独自に相互変換のためのメソッドを定義するよりは、標準ライブラリのトレイトを実装した方が高い汎用性が得られる。 ようにenumから文字列への変換にはstd::fmt::Displayを、文字列からenumへの変換にはstd::str::FromStrを実装すればよい。 設定値のkey-valueがどこかに保存されていると想定し、設定値のkeyをenumで定義するサンプルコードを次に示す。 なお、分かりやすさのためにこのコードではuseを使用していない。

enum ConfigKey {
    MaxMemory,
    MaxProcess,
    AllowEventuallyConsistency,
}

impl std::fmt::Display for ConfigKey {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let s = match *self {
            Self::MaxMemory => "MaxMemory",
            Self::MaxProcess => "MaxProcess",
            Self::AllowEventuallyConsistency => "AllowEventuallyConsistency",
            // 安全のために_を使わない。下手に_で網羅するとバクの温床になる。
        };
        write!(f, "{}", s)
    }
}

impl std::str::FromStr for ConfigKey {
    type Err = &'static str;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "MaxMemory" => Ok(ConfigKey::MaxMemory),
            "MaxProcess" => Ok(ConfigKey::MaxProcess),
            "AllowEventuallyConsistency" => Ok(ConfigKey::AllowEventuallyConsistency),
            _ => Err("undefined configkey or lack of implimention"),
        }
    }
}

fn main() {
    // std::fmt::Displayトレイトの実装を呼び出し、enum variantから文字列へ変換する。
    println!("{}", ConfigKey::AllowEventuallyConsistency);

    // std::str::FromStrトレイトの実装を呼び出し、文字列からenum variantを得る。
    {
        // 有効な文字列を指定して変換を成功させる。
        let valid = "MaxMemory";
        let valid_ret = match valid.parse::<ConfigKey>() {
            Ok(_) => "valid key; ok",
            Err(_) => "invalid key; ng",
        };
        println!("{} is {}", valid, valid_ret);
    }
    {
        // 無効な文字列を指定して変換を失敗させる。
        let invalid = "foo";
        let invalid_ret = match invalid.parse::<ConfigKey>() {
            Ok(_) => "valid key; ok",
            Err(_) => "invalid key; ng",
        };
        println!("{} is {}", invalid, invalid_ret);
    }
}

実行結果

AllowEventuallyConsistency
MaxMemory is valid key; ok
foo is invalid key; ng