【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/