【MacでRust入門】Rust日本語版チュートリアルの要点まとめ ~ 第11回 Enum 列挙型とパターンマッチング
目次
こんにちは。
最近流行りのRustを初めてみよう!ということでチュートリアルを読み始めました。
内容を忘れないように & チュートリアルがTL;DRな方向けにブログ記事に要点をまとめていこうと思います。
前回はRustの構造体についてまとめました。
今回は第11回です。RustのEnum(列挙型)とパターンマッチングについて要点をまとめます。
RustのEnum(列挙型)とは
Enum(列挙型)はその名の通り「取りうる値を列挙」して定義するデータです。
match式によりifによる条件分岐よりも簡潔にパターンマッチングが表現できます。
RustのEnumを使う方法
最も単純な例では、以下のように値を列挙するだけです。これはIPアドレス種類IpAddrKindがV4とV6の2種類しかないことを表しています。
enum IpAddrKind {
V4,
V6,
}
enumによりIpAddrKindというプログラム内で使える独自のデータ型を作れるV4とV6のような列挙されたものを列挙子という
列挙子のインスタンスを作るには以下のようにします。
let four = IpAddrKind::V4;
enumの列挙子には以下のように型を決めて直接データを添付することもできます。
enum IpAddr {
V4(String), // Stringの値が紐付けられることを意味する
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1")); // ()内に値を添付する
このように、enumの型と数は異なっていてもOKです。
enum IpAddr {
V4(u8, u8, u8, u8), // 4つの数値を格納できる
V6(String), // 1つの文字列を格納できる
}
let home = IpAddr::V4(127, 0, 0, 1);
ちなみに、Rustには標準ライブラリにIPアドレスを格納するIpAddrという型の定義があります。V4とV6で異なる構造体が定義されていて、それを列挙子に埋め込んでいます。
また、enumを使うと関連する値やメソッドを定義することが、構造体を使う場合よりも簡単に実装できる場合があります。以下の例ではMessageという単独の型(enum)に異なる型や共通メソッドを集約できます。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
// 関数本体は省略
}
}
このようにimplを使えば構造体同様にメソッド定義が可能です。
標準ライブラリのEnum OptionとNone
Rustの標準ライブラリにOptionという有用なenumがあります。
これはRustにおけるNullのようなもので(RustにNullは存在しません)、ある値が「何かかそうでないか」という基本的な条件分岐をコード化できます。
Option
enum Option<T> {
Some(T),
None,
}
スコープに導入しなくても初期化処理(prelude)に含まれているので、どのRustプログラムでもOption, Some, Noneを使えます。
<T>はジェネリック型引数で(TypeScriptにもありますね)、Some列挙子があらゆるデータ型を1つ持てることを表しています。
None
Noneを使いたい合は以下のように<T>の型が何かであることをセットしないと、Optionが型推論できないため、コンパイルエラーになります。
let x: i32 = 5
let none: Option<i32> = None;
ただし、i32と、Noneを取りうるOption<i32>は異なる型であることに注意です。上の例では、x+noneができません。
逆にいうと、Rustはこのコンパイルエラーがあるために、Nullがあるプログラミング言語よりもバグが起きにくいと言えます。
(整数型なのにNullを取れるとすると、計算できないエラーを出力する、もしくは意図しない計算結果になる可能性があるプログラムになるかもしれません。)
Optionから<T>型の値を取り出したいときは次のmatchを使います。
match式によるRustのパターンマッチング
matchを使うとenumのすべての取りうる値に対するパターンに応じて異なる処理を実行することが可能になります。if elseの簡潔版のようなものです。
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
plus_oneは引数に数字を渡すと1を足した結果を返し、Noneを渡すとNoneを返す関数です。引数のパターンに応じて異なる結果を返します。
match enum { アーム, ... }のように書く- アームは各列挙子のパターンに対する処理で、
列挙子 => { 実行するコード }のように書く - アームで実行するコードが1行で書ける場合、
{}で括らなくて良い - マッチしたアーム式の結果が
match全体の戻り値になる - 網羅性が必要(アームはenumが取りうるすべての列挙子に対して必要)で、足りないとコンパイルエラーになる
_(プレースホルダー)で「残りのパターンすべて」のアームが書ける
プレースホルダーを使った例として、「他のパターンでは何もしない」を書くと以下のようになります。
match some_u8_value { // 0~255までの整数
1 => println!("one"),
3 => println!("three"),
_ => (),
}
上記の例では、enumの数値が1か3以外だと何も処理を行いません。
ある1つのパターンでのみ処理を行いたい場合は次のif letが使えます。
if let 記法によるパターンマッチング
例えば上記のmatchで、数値が1のときのみ処理を行いたい場合は以下のように書けます。
if let Some(1) = some_u8_value {
println!("one");
}
if let 列挙子 = enum {式}と書く- enumがある列挙子のときに式を実行する
- 記述するパターンが減るためコードが簡潔になる(メリット)
- 網羅性がチェックされずにコンパイルが通ってしまう(デメリット)
else {式}を加えて「特定のパターン以外」の処理を書くこともできる
matchだとコードが過剰になってしまう場合は選択肢になりますね。
まとめ
というわけで今回はRustのenum(列挙型)、match、if letによるパターンマッチングついてまとめました。
- enumを使うと取りうる値を列挙した独自の型が作れる
- enumの列挙子の型を決めたり
implでメソッドを定義できる - 標準ライブラリのenum
Option<T>でNullがある言語より安全なエラー回避コードが書ける matchでenumの列挙子を網羅し、各パターンで行う処理を書ける- 特定のパターンのみ処理を行いたい場合は
if letが使える
独自の型を導入することで予想しない値が渡されることがなくなり、よりコードの安全性を高めることができます。
次回はRustのモジュールやクレートについてまとめます。
それでは〜
Rust オススメの参考書籍
▼ 入門者向け。Pythonとの比較あり。Kindleでも読める。
手を動かして考えればよくわかる 高効率言語 Rust 書きかた・作りかた
▼ 中級者向け。オライリーならではの詳解。硬派な方向け。
参考リンク
Rustチュートリアル (日本語版 非公式) https://doc.rust-jp.rs/book-ja/
