パターン記法

本全体で、多くの種類のパターンの例を見かけてきました。この節では、パターンで合法な記法全てを集め、 それぞれを使用したくなる可能性がある理由について議論します。

リテラルにマッチする

第6章で目撃したように、パターンを直接リテラルに合致させられます。以下のコードが例を挙げています:


# #![allow(unused_variables)]
#fn main() {
let x = 1;

match x {
    1 => println!("one"),       // 1
    2 => println!("two"),       // 2
    3 => println!("three"),     // 3
    _ => println!("anything"),  // なんでも
}
#}

このコードは、xの値が1なので、oneを出力します。この記法は、コードが特定の具体的な値を得た時に行動を起こしてほしい時に有用です。

名前付き変数にマッチする

名前付き変数はどんな値にも合致する論駁不可能なパターンであり、この本の中で何度も使用してきました。 ですが、名前付き変数をmatch式で使うと、厄介な問題があります。matchは新しいスコープを開始するので、 match式内のパターンの一部として宣言された変数は、あらゆる変数同様にmatch構文外部の同じ名前の変数を覆い隠します。 リスト18-11で、値Some(5)xという変数と値10の変数yを宣言しています。それから値xに対してmatch式を生成します。 マッチアームのパターンと最後のprintln!を見て、このコードを実行したり、先まで読み進める前にこのコードが何を出力するか推測してみてください。

ファイル名: src/main.rs

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        // 50だったよ
        Some(50) => println!("Got 50"),
        // マッチしたよ
        Some(y) => println!("Matched, y = {:?}", y),
        // 規定のケース
        _ => println!("Default case, x = {:?}", x),
    }

    // 最後にはx = {}, y = {}
    println!("at the end: x = {:?}, y = {:?}", x, y);
}

リスト18-11: シャドーイングされた変数yを導入するアームのあるmatch

match式を実行した時に起こることを見ていきましょう。最初のマッチアームのパターンは、xの定義された値に合致しないので、 コードは継続します。

2番目のマッチアームのパターンは、Some値内部のあらゆる値に合致する新しいyという変数を導入します。 match式内の新しいスコープ内にいるので、これは新しいy変数であり、最初に値10で宣言したyではありません。 この新しいy束縛は、Some内のあらゆる値に合致し、xにあるものはこれです。故に、この新しいyは、 xの中身の値に束縛されます。その値は5なので、そのアームの式が実行され、Matched, y = 5と出力されます。

xSome(5)ではなくNone値だったなら、最初の2つのアームのパターンはマッチしなかったので、 値はアンダースコアに合致したでしょう。アンダースコアのアームのパターンではx変数を導入しなかったので、 その式のxは、まだシャドーイングされない外側のxのままです。この架空の場合、 matchDefault case, x = Noneと出力するでしょう。

match式が完了すると、スコープが終わるので、中のyのスコープも終わります。 最後のprintln!at the end: x = Some(5), y = 10を生成します。

シャドーイングされた変数を導入するのではなく、外側のxyの値を比較するmatch式を生成するには、 代わりにマッチガード条件式を使用する必要があるでしょう。マッチガードについては、後ほど、 「マッチガードで追加の条件式」節で語ります。

複数のパターン

match式で|記法で複数のパターンに合致させることができ、これはorを意味します。例えば、以下のコードはxの値をマッチアームに合致させ、 最初のマッチアームにはor選択肢があり、xの値がそのアームのどちらかの値に合致したら、そのアームのコードが走ることを意味します:


# #![allow(unused_variables)]
#fn main() {
let x = 1;

match x {
    // 1か2
    1 | 2 => println!("one or two"),
    // 3
    3 => println!("three"),
    // なんでも
    _ => println!("anything"),
}
#}

このコードは、one or twoを出力します。

...で値の範囲に合致させる

...記法により、限度値を含む値の範囲にマッチさせることができます。以下のコードでは、 パターンが範囲内のどれかの値に合致すると、そのアームが実行されます:


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

match x {
    // 1から5まで
    1 ... 5 => println!("one through five"),
    // それ以外
    _ => println!("something else"),
}
#}

xが1、2、3、4か5なら、最初のアームが合致します。この記法は、|演算子を使用して同じ考えを表現するより便利です; 1 ... 5ではなく、|を使用したら、1 | 2 | 3 | 4 | 5と指定しなければならないでしょう。 範囲を指定する方が遥かに短いです。特に1から1000までの値と合致させたいとかなら!

範囲は、数値かchar値でのみ許可されます。コンパイラがコンパイル時に範囲が空でないことを確認しているからです。 範囲が空かそうでないかコンパイラにわかる唯一の型がcharか数値なのです。

こちらは、char値の範囲を使用する例です:


# #![allow(unused_variables)]
#fn main() {
let x = 'c';

match x {
    // ASCII文字前半
    'a' ... 'j' => println!("early ASCII letter"),
    // ASCII文字後半
    'k' ... 'z' => println!("late ASCII letter"),
    // それ以外
    _ => println!("something else"),
}
#}

コンパイラにはcが最初のパターンの範囲にあることがわかり、early ASCII letterと出力されます。

分配して値を分解する

またパターンを使用して構造体、enum、タプル、参照を分配し、これらの値の異なる部分を使用することもできます。 各値を見ていきましょう。

構造体を分配する

リスト18-12は、let文でパターンを使用して分解できる2つのフィールドxyのあるPoint構造体を示しています。

ファイル名: src/main.rs

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}

リスト18-12: 構造体のフィールドを個別の変数に分配する

このコードは、p変数のxyフィールドの値に合致する変数abを生成します。この例は、 パターンの変数の名前は、構造体のフィールド名と合致する必要はないことを示しています。しかし、 変数名をフィールド名と一致させてどの変数がどのフィールド由来のものなのか覚えやすくしたくなることは一般的なことです。

変数名をフィールドに一致させることは一般的であり、let Point{ x: x, y: y } = p;と書くことは多くの重複を含むので、 構造体のフィールドと一致するパターンには省略法があります: 構造体のフィールドの名前を列挙するだけで、 パターンから生成される変数は同じ名前になるのです。リスト18-13は、リスト18-12と同じ振る舞いをするコードを表示していますが、 letパターンで生成される変数はabではなく、xyです。

ファイル名: src/main.rs

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

リスト18-13: 構造体フィールド省略法で構造体のフィールドを分配する

このコードは、p変数のxyフィールドに一致する変数xyを生成します。 結果は、変数xyp構造体の値を含むというものです。

また、全フィールドに対して変数を生成するのではなく、リテラル値を構造体パターンの一部にして分配することもできます。 そうすることで他のフィールドは分配して変数を生成しつつ、一部のフィールドは特定の値と一致するか確認できます。

リスト18-14は、Point値を3つの場合に区別するmatch式を表示しています: x軸上の点(y = 0ならそうなる)、 y軸上の点(x = 0)、あるいはどちらでもありません。

ファイル名: src/main.rs

# struct Point {
#     x: i32,
#     y: i32,
# }
#
fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        // x軸上の{}
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        // y軸上の{}
        Point { x: 0, y } => println!("On the y axis at {}", y),
        // どちらの軸上でもない: ({}, {})
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

リスト18-14: 分配とリテラル値との一致を1つのパターンで

最初のアームは、yフィールドの値がリテラル0と一致するならマッチすると指定することで、x軸上にあるどんな点とも一致します。 このパターンはそれでも、このアームのコードで使用できるx変数を生成します。

同様に、2番目のアームは、xフィールドが0ならマッチすると指定することでy軸上のどんな点とも一致し、 yフィールドの値には変数yを生成します。3番目のアームは何もリテラルを指定しないので、 それ以外のあらゆるPointに合致し、xyフィールド両方に変数を生成します。

この例で、値pは0を含むxの力で2番目のアームに一致するので、このコードはOn the y axis at 7と出力します。

enumを分配する

例えば、第6章のリスト6-5でOption<i32>を分配するなどこの本の前半でenumを分配しました。 明示的に触れなかった詳細の1つは、enumを分配するパターンは、enum内に格納されているデータが定義されている手段に対応すべきということです。 例として、リスト18-15では、リスト6-2からMessage enumを使用し、内部の値それぞれを分配するパターンを伴うmatchを書いています。

ファイル名: src/main.rs

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            // Quit列挙子には分配すべきデータがない
            println!("The Quit variant has no data to destructure.")
        },
        Message::Move { x, y } => {
            println!(
                // x方向に{}、y方向に{}だけ動く
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        // テキストメッセージ: {}
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                // 色を(R, G, B) = ({}, {}, {})に変更
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}

リスト18-15: 異なる種類の値を保持するenumの列挙子を分配する

このコードは、Change the color to red 0, green 160, blue 255と出力します。 試しにmsgの値を変更して、他のアームのコードが走るところを確認してください。

Message::Quitのようなデータのないenum列挙子については、それ以上値を分配することができません。 リテラルMessage::Quit値にマッチするだけで、変数はそのパターンに存在しません。

Message::Moveのような構造体に似たenumの列挙子については、構造体と一致させるために指定するパターンと似たパターンを使用できます。 列挙子の名前の後に波括弧を配置し、それから変数とともにフィールドを列挙するので、部品を分解してこのアームのコードで使用します。 ここでは、リスト18-13のように省略形態を使用しています。

1要素タプルを保持するMessage::Writeや、3要素タプルを保持するMessage::ChangeColorのようなタプルに似たenumの列挙子について、 パターンは、タプルと一致させるために指定するパターンと類似しています。パターンの変数の数は、 マッチ対象の列挙子の要素数と一致しなければなりません。

参照を分配する

パターンとマッチさせている値に参照が含まれる場合、値から参照を分配する必要があり、 パターンに&を指定することでそうすることができます。そうすることで参照を保持する変数を得るのではなく、 参照が指している値を保持する変数が得られます。このテクニックは特に、参照を走査するイテレータがあるけれども、 参照ではなく、クロージャで値を使用したいクロージャで特に役に立ちます。

リスト18-16の例は、ベクタのPointインスタンスへの参照を走査し、xy値に簡単に計算を行えるように、 参照と構造体を分配します。


# #![allow(unused_variables)]
#fn main() {
# struct Point {
#     x: i32,
#     y: i32,
# }
#
let points = vec![
    Point { x: 0, y: 0 },
    Point { x: 1, y: 5 },
    Point { x: 10, y: -3 },
];

let sum_of_squares: i32 = points
    .iter()
    .map(|&Point { x, y }| x * x + y * y)
    .sum();
#}

リスト18-16: 構造体への参照を構造体のフィールド値に分配する

このコードは、値135を保持する変数sum_of_squaresを返してきて、これは、x値とy値を2乗し、足し合わせ、 pointsベクタのPointそれぞれの結果を足して1つの数値にした結果です。

&Point { x, y }&が含まれていなかったら、型不一致エラーが発生していたでしょう。 iterはそうすると、実際の値ではなく、ベクタの要素への参照を走査するからです。そのエラーはこんな見た目でしょう:

error[E0308]: mismatched types
  -->
   |
14 |         .map(|Point { x, y }| x * x + y * y)
   |               ^^^^^^^^^^^^ expected &Point, found struct `Point`
   |
   = note: expected type `&Point`
              found type `Point`

このエラーは、コンパイラがクロージャに&Pointと一致することを期待しているのに、 Pointへの参照ではなく、Point値に直接一致させようとしたことを示唆しています。

構造体とタプルを分配する

分配パターンをさらに複雑な方法で混ぜてマッチさせ、ネストすることができます。以下の例は、 構造体とタプルをタプルにネストし、全ての基本的な値を取り出している複雑な分配を表示しています:


# #![allow(unused_variables)]
#fn main() {
# struct Point {
#     x: i32,
#     y: i32,
# }
#
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
#}

このコードは、複雑な型を構成する部品に分配させてくれるので、興味のある値を個別に使用できます。

パターンで分配することは、構造体の各フィールドからの値のように、一部の値を他と区別して使用する便利な方法です。

パターンの値を無視する

matchの最後のアームのように、パターンの値を無視して実際には何もしないけれども、 残りの全ての値の可能性を考慮する包括的なものを得ることは時として有用であると認識しましたね。 値全体やパターンの一部の値を無視する方法はいくつかあります: _パターンを使用すること(もう見かけました)、 他のパターン内で_パターンを使用すること、アンダースコアで始まる名前を使用すること、..を使用して値の残りの部分を無視することです。 これらのパターンそれぞれを使用する方法と理由を探求しましょう。

_で値全体を無視する

どんな値にも一致するけれども、値を束縛しないワイルドカードパターンとしてアンダースコア、_を使用してきました。 アンダースコア、_パターンは特にmatch式の最後のアームとして役に立ちますが、 関数の引数も含めてあらゆるパターンで使えます。リスト18-17に示したようにですね。

ファイル名: src/main.rs

fn foo(_: i32, y: i32) {
    // このコードは、y引数を使うだけです: {}
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}

リスト18-17: 関数シグニチャで_を使用する

このコードは、最初の引数として渡された値3を完全に無視し、This code only uses the y parameter: 4と出力します。

特定の関数の引数が最早必要ないほとんどの場合、未使用の引数が含まれないようにシグニチャを変更するでしょう。 関数の引数を無視することが特に有用なケースもあり、例えば、トレイトを実装する際、 特定の型シグニチャが必要だけれども、自分の実装の関数本体では引数の1つが必要ない時などです。 そうすれば、代わりに名前を使った場合のようには、未使用関数引数についてコンパイラが警告することはないでしょう。

ネストされた_で値の一部を無視する

また、他のパターンの内部で_を使用して、値の一部だけを無視することもでき、例えば、 値の一部だけを確認したいけれども、走らせたい対応するコードでは他の部分を使用することがない時などです。 リスト18-18は、設定の値を管理する責任を負ったコードを示しています。業務要件は、 ユーザが既存の設定の変更を上書きすることはできないべきだけれども、設定をリセットし、 現在設定がされてなければ設定に値を与えられるというものです。


# #![allow(unused_variables)]
#fn main() {
let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        // 既存の値の変更を上書きできません
        println!("Can't overwrite an existing customized value");
    }
    _ => {
        setting_value = new_setting_value;
    }
}

// 設定は{:?}です
println!("setting is {:?}", setting_value);
#}

リスト18-18: Some内の値を使用する必要がない時にSome列挙子と合致するパターンでアンダースコアを使用する

このコードは、Can't overwrite an existing customized value、そしてsetting is Some(5)と出力するでしょう。 最初のマッチアームで、どちらのSome列挙子内部の値にも合致させたり、使用する必要はありませんが、 setting_valuenew_setting_valueSome列挙子の場合を確かに確認する必要があります。 その場合、何故setting_valueを変更しないかを出力し、変更しません。

2番目のアームの_パターンで表現される他のあらゆる場合(setting_valuenew_setting_valueどちらかがNoneなら)には、 new_setting_valueにはsetting_valueになってほしいです。

また、1つのパターンの複数箇所でアンダースコアを使用して特定の値を無視することもできます。 リスト18-19は、5要素のタプルで2番目と4番目の値を無視する例です。


# #![allow(unused_variables)]
#fn main() {
let numbers = (2, 4, 8, 16, 32);

match numbers {
    (first, _, third, _, fifth) => {
        // 何かの数値: {}, {}, {}
        println!("Some numbers: {}, {}, {}", first, third, fifth)
    },
}
#}

リスト18-19: タプルの複数の部分を無視する

このコードは、Some numbers: 2, 8, 32と出力し、値4と16は無視されます。

名前を_で始めて未使用の変数を無視する

変数を作っているのにどこでも使用していなければ、バグかもしれないのでコンパイラは通常、警告を発します。 しかし時として、まだ使用しない変数を作るのが有用なこともあります。プロトタイプを開発したり、 プロジェクトを始めた直後だったりなどです。このような場面では、変数名をアンダースコアで始めることで、 コンパイラに未使用変数について警告しないよう指示することができます。リスト18-20で2つの未使用変数を生成していますが、 このコードを実行すると、そのうちの1つにしか警告が出ないはずです。

ファイル名: src/main.rs

fn main() {
    let _x = 5;
    let y = 10;
}

リスト18-20: アンダースコアで変数名を始めて未使用変数警告が出るのを回避する

ここで、変数yを使用していないことに対して警告が出ていますが、アンダースコアが接頭辞になっている変数には、 使用していないという警告が出ていません。

_だけを使うのとアンダースコアで始まる名前を使うことには微妙な違いがあることに注意してください。 _x記法はそれでも、値を変数に束縛する一方で、_は全く束縛しません。この差異が問題になる場合を示すために、 リスト18-21はエラーを提示するでしょう。

// やあ!
let s = Some(String::from("Hello!"));

if let Some(_s) = s {
    // 文字列が見つかりました
    println!("found a string");
}

println!("{:?}", s);

リスト18-21: それでも、アンダースコアで始まる未使用の変数は値を束縛し、値の所有権を奪う可能性がある

それでもs値は_sにムーブされ、再度sを使用できなくするので、エラーを受け取るでしょう。ですが、 アンダースコアを単独で使用すれば、値を束縛することは全くありません。 s_にムーブされないので、リスト18-22はエラーなくコンパイルできます。


# #![allow(unused_variables)]
#fn main() {
let s = Some(String::from("Hello!"));

if let Some(_) = s {
    println!("found a string");
}

println!("{:?}", s);
#}

リスト18-22: アンダースコアを使用すると、値を束縛しない

このコードは、sを何にも束縛しないので、ただ単に上手く動きます。つまり、ムーブされないのです。

..で値の残りの部分を無視する

多くの部分がある値では、..記法を使用していくつかの部分だけを使用して残りを無視し、 無視する値それぞれにアンダースコアを列挙する必要性を回避できます。..パターンは、 パターンの残りで明示的にマッチさせていない値のどんな部分も無視します。リスト18-23では、 3次元空間で座標を保持するPoint構造体があります。match式でx座標のみ処理し、 yzフィールドの値は無視したいです。


# #![allow(unused_variables)]
#fn main() {
struct Point {
    x: i32,
    y: i32,
    z: i32,
}

let origin = Point { x: 0, y: 0, z: 0 };

match origin {
    Point { x, .. } => println!("x is {}", x),
}
#}

リスト18-23: ..x以外のPointのフィールド全てを無視する

x値を列挙し、それから..パターンを含んでいるだけです。これは、y: _z: _と列挙しなければいけないのに比べて、 手っ取り早いです。特に1つや2つのフィールドのみが関連する場面で多くのフィールドがある構造体に取り掛かっている時には。

..記法は、必要な数だけ値に展開されます。リスト18-24は、タプルで..を使用する方法を表示しています。

ファイル名: src/main.rs

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {}, {}", first, last);
        },
    }
}

リスト18-24: タプルの最初と最後の値にだけ合致し、他の値を無視する

このコードにおいて、最初と最後の値はfirstlastに合致します。..は、 途中のもの全部に合致し、無視します。

しかしながら、..を使うのは明確でなければなりません。どの値がマッチしてどの値が無視されるべきかが不明瞭なら、 コンパイラはエラーを出します。リスト18-25は、..を曖昧に使用する例なので、コンパイルできません。

ファイル名: src/main.rs

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}

リスト18-25: ..を曖昧に使用しようとする試み

この例をコンパイルすると、こんなエラーが出ます:

error: `..` can only be used once per tuple or tuple struct pattern
(エラー: `..`は、タプルやタプル構造体パターン1つにつき、1回しか使用できません)
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |                      ^^

コンパイラが、secondの値に合致する前にタプルの幾つの値を無視し、それからそれによってさらに幾つの値を無視するかを決めることは不可能です。 このコードは、2を無視し、second4を束縛し、それから81632を無視したり、 24を無視してsecond8を束縛し、それから1632を無視するなどを意味することもあるでしょう。 変数名のsecondは、コンパイラにとってなんの特別な意味もなく、このように2箇所で..を使うのは曖昧なので、 コンパイルエラーになります。

refref mutでパターンで参照を生成する

refを使用して値の所有権がパターンの変数にムーブされないように、参照を生成することに目を向けましょう。 通常、パターンにマッチさせると、パターンで導入された変数は値に束縛されます。Rustの所有権規則は、 その値がmatchなどパターンを使用しているあらゆる場所にムーブされることを意味します。 リスト18-26は、変数があるパターンとそれからmatchの後に値全体をprintln!文で後ほど使用するmatchの例を示しています。 このコードはコンパイルに失敗します。robot_name値の一部の所有権が、 最初のmatchアームのパターンのname変数に移るからです。

let robot_name = Some(String::from("Bors"));

match robot_name {
    // 名前が見つかりました: {}
    Some(name) => println!("Found a name: {}", name),
    None => (),
}

// robot_nameは: {:?}
println!("robot_name is: {:?}", robot_name);

リスト18-26: matchアームパターンで変数を生成すると、値の所有権が奪われる

robot_nameの一部の所有権がnameにムーブされたので、robot_nameに最早所有権がないために、 matchの後にprintln!で最早robot_nameを使用することは叶いません。

このコードを修正するために、Some(name)パターンに所有権を奪わせるのではなく、 robot_nameのその部分を借用させたいです。パターンの外なら、値を借用する手段は、 &で参照を生成することだと既にご認識でしょうから、解決策はSome(name)Some(&name)に変えることだとお考えかもしれませんね。

しかしながら、「分配して値を分解する」節で見かけたように、パターンにおける&記法は参照を生成せず、 値の既存の参照にマッチします。パターンにおいて&には既にその意味があるので、 &を使用してパターンで参照を生成することはできません。

その代わりに、パターンで参照を生成するには、リスト18-27のように、新しい変数の前にrefキーワードを使用します。


# #![allow(unused_variables)]
#fn main() {
let robot_name = Some(String::from("Bors"));

match robot_name {
    Some(ref name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);
#}

リスト18-27: パターンの変数が値の所有権を奪わないように参照を生成する

robot_nameSome列挙子の値がmatchにムーブされないので、この例はコンパイルできます; matchはムーブするのではなく、robot_nameのデータへの参照を取っただけなのです。

パターンで合致した値を可変化できるように可変参照を生成するには、&mutの代わりにref mutを使用します。 理由は今度も、パターンにおいて、前者は既存の可変参照にマッチするためにあり、新しい参照を生成しないからです。 リスト18-28は、可変参照を生成するパターンの例です。


# #![allow(unused_variables)]
#fn main() {
let mut robot_name = Some(String::from("Bors"));

match robot_name {
    // 別の名前
    Some(ref mut name) => *name = String::from("Another name"),
    None => (),
}

println!("robot_name is: {:?}", robot_name);
#}

リスト18-28: ref mutを使用して、パターンの一部として値への可変参照を生成する

この例はコンパイルが通り、robot_name is: Some("Another name")と出力するでしょう。 nameは可変参照なので、マッチアーム内で*演算子を使用して値を可変化するために参照外しする必要があります。

マッチガードで追加の条件式

マッチガードは、matchアームのパターンの後に指定されるパターンマッチングとともに、 そのアームが選択されるのにマッチしなければならない追加のif条件です。マッチガードは、 1つのパターン単独でできるよりも複雑な考えを表現するのに役に立ちます。

この条件は、パターンで生成された変数を使用できます。リスト18-29は、 最初のアームにパターンSome(x)if x < 5というマッチガードもあるmatchを示しています。


# #![allow(unused_variables)]
#fn main() {
let num = Some(4);

match num {
    // 5未満です: {}
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}
#}

リスト18-29: パターンにマッチガードを追記する

この例は、less than five: 4と出力します。numが最初のアームのパターンと比較されると、 Some(4)Some(x)に一致するので、マッチします。そして、マッチガードがxの値が5未満か確認し、 そうなっているので、最初のアームが選択されます。

代わりにnumSome(10)だったなら、最初のアームのマッチガードは偽になったでしょう。 10は5未満ではないからです。Rustはそうしたら2番目のアームに移動し、マッチするでしょう。 2番目のアームにはマッチガードがなく、それ故にあらゆるSome列挙子に一致するからです。

パターン内でif x < 5という条件を表現する方法はありませんので、マッチガードにより、 この論理を表現する能力が得られるのです。

リスト18-11において、マッチガードを使用すれば、パターンがシャドーイングする問題を解決できると述べました。 matchの外側の変数を使用するのではなく、match式のパターン内部では新しい変数が作られることを思い出してください。 その新しい変数は、外側の変数の値と比較することができないことを意味しました。リスト18-30は、 マッチガードを使ってこの問題を修正する方法を表示しています。

ファイル名: src/main.rs

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {:?}", n),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}

リスト18-30: マッチガードを使用して外側の変数と等しいか確認する

このコードは今度は、Default case, x = Some(5)と出力するでしょう。2番目のマッチアームのパターンは、 外側のyを覆い隠してしまう新しい変数yを導入せず、マッチガード内で外側のyを使用できることを意味します。 外側のyを覆い隠してしまうSome(y)としてパターンを指定するのではなく、Some(n)を指定しています。 これにより、何も覆い隠さない新しい変数nが生成されます。matchの外側にはn変数は存在しないからです。

マッチガードのif n == yはパターンではなく、故に新しい変数を導入しません。このyは、 新しいシャドーイングされたyではなく、外側のyでありnyを比較することで、 外側のyと同じ値を探すことができます。

また、マッチガードでor演算子の|を使用して複数のパターンを指定することもできます; マッチガードの条件は全てのパターンに適用されます。リスト18-31は、 |を使用するパターンとマッチガードを組み合わせる優先度を示しています。この例で重要な部分は、 if y6にしか適用されないように見えるのに、if yマッチガードが45そして6に適用されることです。


# #![allow(unused_variables)]
#fn main() {
let x = 4;
let y = false;

match x {
    // はい
    4 | 5 | 6 if y => println!("yes"),
    // いいえ
    _ => println!("no"),
}
#}

リスト18-31: 複数のパターンとマッチガードを組み合わせる

マッチの条件は、xの値が456に等しくかつytrueの場合だけにアームがマッチすると宣言しています。 このコードが走ると、最初のアームのパターンはx4なので、合致しますが、マッチガードif yは偽なので、 最初のアームは選ばれません。コードは2番目のアームに移動して、これがマッチし、このプログラムはnoと出力します。 理由は、if条件が最後の値の6だけでなく、パターン全体4 | 5 | 6に適用されるからです。 言い換えると、パターンと関わるマッチガードの優先度は、以下のように振る舞います:

(4 | 5 | 6) if y => ...

以下のようにではありません:

4 | 5 | (6 if y) => ...

コードを実行後には、優先度の動作は明らかになります: マッチガードが|演算子で指定される値のリストの最後の値にしか適用されないなら、 アームはマッチし、プログラムはyesと出力したでしょう。

@束縛

at演算子(@)により、値を保持する変数を生成するのと同時にその値がパターンに一致するかを調べることができます。 リスト18-32は、Message::Helloidフィールドが範囲3...7にあるかを確かめたいという例です。 しかし、アームに紐づいたコードで使用できるように変数id_variableに値を束縛もしたいです。この変数をフィールドと同じ、 idと名付けることもできますが、この例では異なる名前にします。


# #![allow(unused_variables)]
#fn main() {
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello { id: id_variable @ 3...7 } => {
        // 範囲内のidが見つかりました: {}
        println!("Found an id in range: {}", id_variable)
    },
    Message::Hello { id: 10...12 } => {
        // 別の範囲内のidが見つかりました
        println!("Found an id in another range")
    },
    Message::Hello { id } => {
        // それ以外のidが見つかりました
        println!("Found some other id: {}", id)
    },
}
#}

@を使用してテストしつつ、パターンの値に束縛する

この例は、Found an id in range: 5と出力します。範囲3...7の前にid_variable @と指定することで、 値が範囲パターンに一致することを確認しつつ、範囲にマッチしたどんな値も捕捉しています。

パターンで範囲しか指定していない2番目のアームでは、アームに紐づいたコードにidフィールドの実際の値を含む変数はありません。 idフィールドの値は10、11、12だった可能性もありますが、そのパターンに来るコードは、 どれなのかわかりません。パターンのコードはidフィールドの値を使用することは叶いません。 idの値を変数に保存していないからです。

範囲なしに変数を指定している最後のアームでは、確かにアームのコードで使用可能な値がidという変数にあります。 理由は、構造体フィールド省略記法を使ったからです。しかし、このアームでidフィールドの値に対して、 最初の2つのアームのようには、確認を行っていません: どんな値でも、このパターンに一致するでしょう。

@を使用することで、値を調べつつ、1つのパターン内で変数に保存させてくれるのです。

まとめ

Rustのパターンは、異なる種類のデータを区別するのに役立つという点でとても有用です。match式で使用されると、 コンパイラはパターンが全ての可能性を網羅しているか保証し、そうでなければプログラムはコンパイルできません。 let文や関数の引数のパターンは、その構文をより有用にし、値を分配して小さな部品にすると同時に変数に代入できるようにしてくれます。 単純だったり複雑だったりするパターンを生成してニーズに合わせることができます。

次の本の末尾から2番目の章では、Rustのいろんな機能の高度な視点に目を向けます。