enum
を使った列挙型
enum
を使うと、一つの名前付き列挙型に、複数の型情報を含めた上で、そのうちの一つを選ばせることができます。
列挙型の一つ一つの型情報は互いに異なるものとして扱われます。
Rustの列挙型の特徴として、各要素の型を柔軟に決められる、という点があります。すなわち、ある列挙型のうちある要素は具体的なString
の値を持ち、また別の要素は具体的な(i32, i32)
の値を持つ、といったことが可能です(もちろん、何も値を持たせずに要素名だけ定義することも可能です!)。
enum Message {
Text(String),
Move { x: i32, y: i32 },
Quit,
}
そしてそれらは一つの列挙型という同じ型として扱えるので、例えば関数の引数の型として用いることができます(下記のコードは後述するパターンマッチングを使用しています)。
fn print_message(msg: Message) {
match msg {
Message::Text(content) => println!("Text: {}", content),
Message::Move { x, y } => println!("Move to: ({}, {})", x, y),
Message::Quit => println!("Quit"),
}
}
列挙型を実際に参照する場合は、列挙型::要素名
という記法を用います
fn main() {
let msg1 = Message::Text(String::from("Hello"));
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Quit;
print_message(msg1);
print_message(msg2);
print_message(msg3);
}
この機構は非常に興味深いです。TypeScriptには標準のEnumがありますが、正直なところ利用シーンは限られ、実際はオブジェクトリテラルで代用することが多いでしょう。それに比べると、言語の仕様としてこのような柔軟な機構を持つのは(実際に使うかはともかく)特徴的です。
Option
という列挙型
Option
はRustが標準で提供する列挙型です。その実装は以下のようになっています。
enum Option<T> {
Some(T),
None,
}
任意の型T
について、値が存在していればSome
という要素にその値が格納されています。もし存在していなければOption<T>
はNone
になります。
Option
を使うメリットは、安全にNullableなデータを表現できることにあります。T
とOption<T>
は明確に違う型なので、Option
型の変数を、あたかも値が必ず存在しているように扱うことはできません(コンパイルエラーになります)。この機構により、値の存在有無を確認するコードが強制され、エラーを防ぐことができます。
fn divide(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
fn main() {
match divide(10, 2) {
Some(result) => println!("Result: {}", result),
None => println!("Cannot divide by zero"),
}
}
列挙型とパターンマッチング
列挙型とmatch
を組み合わせることで、強力なパターンマッチングが可能になります。
match
は列挙型にも対応しており、要素に応じて処理を分けることができます。
列挙型の要素が値を持っている場合、要素名(変数名)
のようにしてその値を参照することもできます。
enum Shape {
Circle(f64),
Rectangle { width: f64, height: f64 },
Triangle(f64, f64, f64),
}
fn describe_shape(shape: Shape) {
match shape {
Shape::Circle(radius) => println!("Circle with radius {}", radius),
Shape::Rectangle { width, height } => {
println!("Rectangle with width {} and height {}", width, height)
}
Shape::Triangle(a, b, c) => println!("Triangle with sides {}, {}, {}", a, b, c),
}
}
fn main() {
let shape = Shape::Rectangle { width: 10.0, height: 5.0 };
describe_shape(shape);
}
Option
と組み合わせると、「値が存在するとき」と「そうでないとき」のフローを分けることができます。値が存在するときはSome
に値が保持されているので、Some(変数名)
とすることでその値を参照できます。
fn print_number(opt: Option<i32>) {
match opt {
Some(value) => println!("Number: {}", value),
None => println!("No number provided"),
}
}
fn main() {
print_number(Some(42));
print_number(None);
}
さらに、「値が存在し、かつそれが特定の値のとき」のみ処理を実行したい場合は、if let
構文が使えます。条件にマッチしたときのみ、その値を変数にバインディングしたうえで処理を実行できるため、match
を使うよりも簡潔に記述できます。
fn main() {
let opt = Some(42);
if let Some(42) = opt {
println!("The answer to life, the universe, and everything!");
}
}
Option
は強力な型安全システムのように感じます。TypeScriptでも型レベルでundefined
やnull
を含めることができますが、Rustが持つパターンマッチングとの相性の良さはすごいと感じます。