パターンが使用されることのある箇所全部

Rustにおいて、パターンはいろんな箇所に出現し、そうと気づかないうちにたくさん使用してきました! この節は、パターンが合法な箇所全部を議論します。

matchアーム

第6章で議論したように、パターンをmatch式のアームで使います。正式には、match式はキーワードmatch、 マッチ対象の値、パターンとそのアームのパターンに値が合致したら実行される式からなる1つ以上のマッチアームとして定義されます。 以下のように:

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

match式の必須事項の1つは、match式の値の可能性全てが考慮されなければならないという意味で網羅的である必要があることです。 全可能性をカバーしていると保証する1つの手段は、最後のアームに包括的なパターンを入れることです: 例えば、どんな値にも合致する変数名は失敗することがあり得ないので、故に残りの全ケースをカバーできます。

_という特定のパターンは何にでもマッチしますが、変数には束縛されないので、よく最後のマッチアームに使用されます。 例えば、_パターンは、指定されていないあらゆる値を無視したい時に有用です。 _パターンについて詳しくは、この章の後ほど、「パターンで値を無視する」節で講義します。

条件分岐if let

第6章で主にif let式を1つの場合にしか合致しないmatchと同様のものを書く省略法として使用する方法を議論しました。 オプションとして、if letにはif letのパターンが合致しない時に走るコードを含む対応するelseも用意できます。

リスト18-1は、if letelse ifelse if let式を混ぜてマッチさせることもできることを示しています。 そうすると、パターンと1つの値しか比較することを表現できないmatch式よりも柔軟性が高くなります。 また、一連のif letelse ifelse if letアームの条件は、お互いに関連している必要はありません。

リスト18-1のコードは、背景色が何になるべきかを決定するいくつかの条件を連なって確認するところを示しています。 この例では、実際のプログラムではユーザ入力を受け付ける可能性のある変数をハードコードされた値で生成しています。

ファイル名: src/main.rs

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        // あなたのお気に入りの色、{}を背景色に使用します
        println!("Using your favorite color, {}, as the background", color);
    } else if is_tuesday {
        // 火曜日は緑の日!
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            // 紫を背景色に使用します
            println!("Using purple as the background color");
        } else {
            // オレンジを背景色に使用します
            println!("Using orange as the background color");
        }
    } else {
        // 青を背景色に使用します
        println!("Using blue as the background color");
    }
}

リスト18-1: if letelse ifelse if letelseを混ぜる

ユーザがお気に入りの色を指定したら、その色が背景色になります。今日が火曜日なら、背景色は緑です。 ユーザが年齢を文字列で指定し、数値として解析することができたら、背景色は、その数値の値によって紫かオレンジになります。 どの条件も適用できなければ、背景色は青になります:

この条件分岐構造により、複雑な要件をサポートさせてくれます。ここにあるハードコードされた値では、 この例はUsing purple as the background colorと出力するでしょう。

matchアームのようにif letもシャドーイングされた変数を導入できることがわかります: if let Ok(age) = ageの行は、Ok列挙子の中の値を含むシャドーイングされた新しいage変数を導入します。 つまり、if age > 30という条件は、そのブロック内に配置する必要があります: これら2つの条件を組み合わせて、 if let Ok(age) = age && age > 30とすることはできません。30と比較したいシャドーイングされたageは、 波括弧で新しいスコープが始まるまで有効にならないのです。

if let式を使うことの欠点は、コンパイラが網羅性を確認してくれないことです。一方でmatch式ではしてくれます。 最後のelseブロックを省略して故に、扱い忘れたケースがあっても、コンパイラは、ロジックバグの可能性を指摘してくれないでしょう。

while let条件分岐ループ

if letと構成が似て、while let条件分岐ループは、パターンが合致し続ける限り、whileループを走らせます。 リスト18-2の例は、ベクタをスタックとして使用するwhile letループを示し、 ベクタの値をプッシュしたのとは逆順に出力します:


#![allow(unused)]
fn main() {
let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{}", top);
}
}

リスト18-2: while letループを使ってstack.pop()Someを返す限り値を出力する

この例は、3, 2, そして1と出力します。popメソッドはベクタの最後の要素を取り出してSome(value)を返します。 ベクタが空なら、popNoneを返します。whileループはpopSomeを返す限り、ブロックのコードを実行し続けます。 popNoneを返すと、ループは停止します。while letを使用してスタックから全ての要素を取り出せるのです。

forループ

第3章で、Rustコードにおいては、forループが最もありふれたループ構造だと述べましたが、 forが取るパターンについてはまだ議論していませんでした。forループにおいて、 直接キーワードforに続く値がパターンなので、for x in yでは、xがパターンになります。

リスト18-3はforループでパターンを使用してforループの一部としてタプルを分配あるいは、分解する方法をデモしています。


#![allow(unused)]
fn main() {
let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}
}

リスト18-3: forループでパターンを使用してタプルを分配する

リスト18-3のコードは、以下のように出力するでしょう:

a is at index 0
b is at index 1
c is at index 2

enumerateメソッドを使用してイテレータを改造し、値とその値のイテレータでの添え字をタプルに配置して生成しています。 enumerateの最初の呼び出しは、タプル(0, 'a')を生成します。この値がパターン(index, value)とマッチさせられると、 index0value'a'になり、出力の最初の行を出力するのです。

let

この章に先駆けて、matchif letでパターンを使用することだけ明示的に議論してきましたが、 実はlet文を含む他の箇所でもパターンを使用してきたのです。例として、このletでの率直な変数代入を考えてください:


#![allow(unused)]
fn main() {
let x = 5;
}

この本を通してこのようなletを何百回も使用してきて、お気付きではなかったかもしれませんが、 パターンを使用していたのです!より正式には、let文はこんな見た目をしています:

let PATTERN = EXPRESSION;

let x = 5;のような変数名がPATTERNスロットにある文で、変数名は、ただ特に単純な形態のパターンなのです。 Rustは式をパターンと比較し、見つかったあらゆる名前を代入します。故に、let x = 5;の例では、 xは「ここでマッチしたものを変数xに束縛する」ことを意味するパターンです。 名前xがパターンの全容なので、このパターンは実質的に「値が何であれ、全てを変数xに束縛しろ」を意味します。

letのパターンマッチングの観点をよりはっきり確認するためにリスト18-4を考えてください。 これはletでパターンを使用し、タプルを分配します。


#![allow(unused)]
fn main() {
let (x, y, z) = (1, 2, 3);
}

リスト18-4: パターンを使用してタプルを分配し、3つの変数を一度に生成する

ここでタプルに対してパターンをマッチさせています。Rustは値(1, 2, 3)をパターン(x, y, z)と比較し、 値がパターンに合致すると確認するので、1xに、2yに、3zに束縛します。 このタプルパターンを個別の3つの変数パターンが内部にネストされていると考えることもできます。

パターンの要素数がタプルの要素数と一致しない場合、全体の型が一致せず、コンパイルエラーになるでしょう。 例えば、リスト18-5は、3要素のタプルを2つの変数に分配しようとしているところを表示していて、動きません。

let (x, y) = (1, 2, 3);

リスト18-5: 変数がタプルの要素数と一致しないパターンを間違って構成する

このコードのコンパイルを試みると、このような型エラーに落ち着きます:

error[E0308]: mismatched types
 --> src/main.rs:2:9
  |
2 |     let (x, y) = (1, 2, 3);
  |         ^^^^^^ expected a tuple with 3 elements, found one with 2 elements
  |                (3要素のタプルを予期したのに、2要素のタプルが見つかりました)
  |
  = note: expected type `({integer}, {integer}, {integer})`
             found type `(_, _)`

タプルの値のうち1つ以上を無視したかったら、「パターンで値を無視する」節で見かけるように、 _..を使用できるでしょう。パターンに変数が多すぎるというのが問題なら、変数の数がタプルの要素数と一致するように変数を減らすことで、 型を一致させることが解決策です。

関数の引数

関数の引数もパターンにできます。リスト18-6のコードは、型i32xという引数1つを取るfooという関数を宣言していますが、 これまでに馴染み深くなっているはずです。


#![allow(unused)]
fn main() {
fn foo(x: i32) {
    // コードがここに来る
    // code goes here
}
}

リスト18-6: 関数シグニチャが引数にパターンを使用している

xの部分がパターンです!letのように、関数の引数でパターンにタプルを合致させられるでしょう。 リスト18-7では、タプルを関数に渡したのでその中の値を分離しています。

ファイル名: src/main.rs

fn print_coordinates(&(x, y): &(i32, i32)) {
    // 現在の位置: ({}, {})
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

リスト18-7: タプルを分配する引数を伴う関数

このコードはCurrent location: (3, 5)と出力します。値&(3, 5)はパターン&(x, y)と合致するので、 xは値3yは値5になります。

また、クロージャの引数リストでも、関数の引数リストのようにパターンを使用することができます。 第13章で議論したように、クロージャは関数に似ているからです。

この時点で、パターンを使用する方法をいくつか見てきましたが、パターンを使用できる箇所全部で同じ動作をするわけではありません。 パターンが論駁不可能でなければならない箇所もあります。他の状況では、論駁可能にもなり得ます。この2つの概念を次に議論します。

関連キーワード:  パターン, let, リスト, 変数, ループ, コード, else, 箇所, 関数, 要素