Rustのenumは、値(バリアント)だけでなく、関連データ(ペイロード)も格納できます。これにより、状態やパラメータを持つメッセージを表現するための代数的データ型を作成するのに特に便利です。例えば:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
このようなenumを処理するには、通常match演算子を使用して、各バリアントのネストされた値をアンパックします:
let msg = Message::Move { x: 10, y: 20 }; match msg { Message::Quit => println!("Quit message"), Message::Move { x, y } => println!("Move to ({}, {})", x, y), Message::Write(text) => println!("Text: {}", text), Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b), }
このアプローチにより、すべての可能なケースを明示的かつ安全に処理できるため、プログラムのロジックにおけるエラーを最小限に抑えることができます。
Rustにはenumのnullが存在しないのはなぜですか、また存在しないバリアントとのマッチング試行で何が返されるか?
よくある誤った回答:"コンパイラはすべてのenumバリアントをmatchで処理しないとエラーをスルーします。"
実際には、すべての可能なバリアントが考慮されていない場合、コンパイラはエラーを生成するか、catch-all(_)を追加することを要求します。例えば:
enum Status { Ok, Err(String) } let st = Status::Ok; match st { Status::Ok => println!("Ok"), // Status::Errを忘れると、コンパイラが文句を言います }
事例 あるプロジェクトで、enumを拡張する際に新しいバリアントの処理をmatchのすべての場所で追加しなかったため、リリース時にpanic!が発生しました。これは
matchが網羅的でなかったためであり、テストはすべてのケースをカバーしていませんでした。
事例 別のケースでは、関連データを持つ複雑なバリアントのPartialEqを実装せずにenumを通常の
==で比較しようとしたため、予期しないコンパイルエラーが発生しました。
事例 チームはバリアントを処理するためにcatch-all(
_)を使用し、新しいバリアントを追加した際に、ロジックが新しいケースを静かに無視する結果となり、バグの発見が難しくなりました。