はじめに
- Rust の列挙体はデータを持てるというのがユニークですし、パターンマッチングと組み合わせるとパワフルでとても便利です。
- ここではそんな列挙体の使い方を逆引き形式でまとめてみました。
単純な列挙体を宣言する・生成する・評価する
# [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);
}
データを持つ列挙体を宣言する・生成する・評価する
# [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);
}
列挙体の比較をする
-
PartialOrd
とPartialEq
を使うとヴァリアント同士での比較ができるようになります。 - データ付きヴァリアント同士が持っているデータに関係なく同じであるかどうか判定したい場合は
std::mem::discriminant
を使います。
# [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)), "違うヴァリアント同じデータで異値判定");
}
列挙体から文字列へ
- enum で
std::fmt::Display
を実装すると文字列への変換ができます
# [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}]", "データも正常に反映される");
}
文字列から列挙体へ
- enum で
std::str::FromStr
を実装すると文字列からの変換ができます
# [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 も使える");
}