【Rust】Enumとmatchがよく分からん。という人に向けて ~Optionを添えて~
はじめに
本記事はrust book 6章を元に、もう少し詳しくに解説しています。
コードサンプルや解説をもっと知りたいという方を対象としています。
Enum
RustでEnumは以下のように定義できます。
enum IpAddrKind {
V4,
V6,
}
そしてそれぞれを出力すると、V4
,V6
と表示されます。
#[derive(Debug)]
enum IpAddrKind {
V4,
V6,
}
fn main() {
println!("{:?}", IpAddrKind::V4); // V4
println!("{:?}", IpAddrKind::V6); // V6
}
Enumは個別に型を持てる
Enumはそれぞれのvariantに値を持つことができます。
そのため、Enumを包含した余計な型を作る手間を省くことができます。
#[derive(Debug)]
enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
println!("{:?}", IpAddrKind::V4(127, 0, 0, 1)); // V4(127, 0, 0, 1)
println!("{:?}", IpAddrKind::V6(String::from("::1"))); // V6("::1")
}
メソッドを定義できる
enumは構造体と同じようにメソッドを実装できます。
先ほどの例のprintln!
を共通化してみましょう。
call()
メソッドを定義しました。
#[derive(Debug)]
enum IpAddrKind {
V4(u8, u8, u8, u8),
V6(String),
}
// メソッドを定義
impl IpAddrKind {
fn call(&self) {
println!("{:?}", self);
}
}
fn main() {
IpAddrKind::V4(127, 0, 0, 1).call(); // V4(127, 0, 0, 1)
IpAddrKind::V6(String::from("::1")).call(); // V6("::1")
}
match
Rustにはパターンマッチングのためのmatchと呼ばれる制御フロー構文があります。Goのswitch
文と似ています。
enum Color {
Red,
Blue,
Green,
}
fn main() {
let color = Color::Red;
let color_jp = color_to_jp(color);
println!("{}", color_jp); // あか
}
fn color_to_jp(color: Color) -> String {
match color {
Color::Red => String::from("あか"),
Color::Blue => String::from("あお"),
Color::Green => String::from("みどり"),
}
}
Enumに個別の型がある場合
Enumには個別に値を持てると前述しましたが、その場合はmatchの中で()
を書くことで引数のように受け取れます。
値が不要な場合はColor::Red(_)
やColor::Red(..)
と記述します。
赤だけにカラーコードを持たせてみましょう。そして、matchした場合はカラーコードをprintするようにコードを書き直します。
#[allow(dead_code)]
enum Color {
Red(String),
Blue,
Green,
}
fn main() {
let color = Color::Red(String::from("#ff0000"));
let color_jp = color_to_jp(color);
println!("{}", color_jp); // あか
}
fn color_to_jp(color: Color) -> String {
match color {
Color::Red(color_code) => {
println!("{}", color_code); // #ff0000
String::from("あか")
}
Color::Blue => String::from("あお"),
Color::Green => String::from("みどり"),
}
}
matchは全てのケースを書くこと
matchでは全てのケースを書かないとエラーとなります。そこで、個別に書かなくていい場合は_
(アンダースコア)を使用して、その他をまとめて表現できます。
#[allow(dead_code)]
enum Color {
Red,
Blue,
Green,
}
fn main() {
let color = Color::Blue;
let color_jp = color_to_jp(color);
println!("{}", color_jp); // その他
}
fn color_to_jp(color: Color) -> String {
match color {
Color::Red => String::from("あか"),
_ => String::from("その他"),
}
}
Enum以外にも利用できる
ここまでEnumのmatchについて説明しましたが、Enum以外にも使用することができます。
fn main() {
let number = 2;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Other"),
}
}
Optionを使いこなそう
ご存知の通り、RustにはNullがありません。その代わりにこのOptionを使います。
そして中身はシンプルです。2つのバリアンとしかありません。
Nullになる可能性がある値は、Optionでラップした型を使います。
値がある場合はSome
, 値がない場合はNone
を設定します。
enum Option<T> {
Some(T),
None,
}
使い方
値がある場合はSome(値)
としてSomeの中に値を格納します。
値が無い場合はNone
として無いことを表現します。
match
を使って、Some
とNone
のEnumでパターンマッチングを行います。
fn main() {
let some_value = Some(String::from("Hello")); // Someは値がある
let none_value: Option<String> = None; // Noneは値が無い
println!("{}", response(some_value)); // Hello World
println!("{}", response(none_value)); // No value
}
fn response(value: Option<String>) -> String {
match value {
Some(value) => {
value + " World"
}
None => String::from("No value"),
}
}
Discussion
マサカリ
本題
「 Enumはそれぞれのvariantに値を持つことができます。」あたりがここの適当な表現ではないでしょうか (?)
はおそらく誤解を含んでいて、enum の variant も名前付きフィールドを持てます (
) し、struct が必ずしも名前のあるフィールドを持っているとは限りません (
) 。また、
は
()
を書いても書かなくてもいいように読めますが、の
Red
にマッチさせる場合、対応するColor::Red( /* 〜 */ )
というパターンを書く必要があります ( 中の値がいらなければColor::Red(_)
やColor::Red(..)
とします ) 。その他
match
は演算子ではないと思います。「制御フロー構文」ですね必ずしも
_
で捨てる必要はなくて、とすることもできます。
これは typo に近そうですが、number 型ではないです ( 一応 )
は誤解を招く書き方だと思います。一般に
Option::None
はいわゆる null とは違うものなので、「値がない可能性があるときは、Optionでラップした型を使います。
値がある場合はSome(値), 値がない場合はNoneで表現します。」
あたりが適当な説明ではないでしょうか (?)
補足
Rust の enum はタグ付き union で、
Some
とNone
自体は単なるタグです。( その上でコンパイラによる最適化の一環としてNone
が null と同一視される場合もありますが ) プログラマ側のマインドセットとしては「使い方」の項のが全てで、そもそも null という概念を意識する必要がないです。
お願い
初学者向けの記事とはいえ事実と違う点は指摘するべきかなと思ってマサカリを投げてみましたが、Rust に興味を持って記事を書く人がいるのは純粋に嬉しいので、どうかこんなことで萎縮せず Rust に関わり続けてほしいです... !
ありがとうございます!
自分が知らないこともあり、とても助かります。
一点、
制御フロー演算子
はRust book 6.2に記述してあり、ここに合わせて書いた経緯があります。構文
の方が自然でしょうか?原文を見る限り "operator" という語句は使われておらず、 "construct" なので「構文」の方が適切かと思います。日本語訳が演算子になっているのは誤訳か、もしくは原文がある時点までoperatorだったのか……
どうやら原文はここで修正されたようですね
ありがとうございます!🙇♂️