パターン記法
本全体で、多くの種類のパターンの例を見かけてきました。この節では、パターンで合法な記法全てを集め、 それぞれを使用したくなる可能性がある理由について議論します。
リテラルにマッチする
第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); }
match
式を実行した時に起こることを見ていきましょう。最初のマッチアームのパターンは、x
の定義された値に合致しないので、
コードは継続します。
2番目のマッチアームのパターンは、Some
値内部のあらゆる値に合致する新しいy
という変数を導入します。
match
式内の新しいスコープ内にいるので、これは新しいy
変数であり、最初に値10で宣言したy
ではありません。
この新しいy
束縛は、Some
内のあらゆる値に合致し、x
にあるものはこれです。故に、この新しいy
は、
x
の中身の値に束縛されます。その値は5
なので、そのアームの式が実行され、Matched, y = 5
と出力されます。
x
がSome(5)
ではなくNone
値だったなら、最初の2つのアームのパターンはマッチしなかったので、
値はアンダースコアに合致したでしょう。アンダースコアのアームのパターンではx
変数を導入しなかったので、
その式のx
は、まだシャドーイングされない外側のx
のままです。この架空の場合、
match
はDefault case, x = None
と出力するでしょう。
match
式が完了すると、スコープが終わるので、中のy
のスコープも終わります。
最後のprintln!
はat the end: x = Some(5), y = 10
を生成します。
シャドーイングされた変数を導入するのではなく、外側のx
とy
の値を比較する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つのフィールドx
とy
のある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); }
このコードは、p
変数のx
とy
フィールドの値に合致する変数a
とb
を生成します。この例は、
パターンの変数の名前は、構造体のフィールド名と合致する必要はないことを示しています。しかし、
変数名をフィールド名と一致させてどの変数がどのフィールド由来のものなのか覚えやすくしたくなることは一般的なことです。
変数名をフィールドに一致させることは一般的であり、let Point{ x: x, y: y } = p;
と書くことは多くの重複を含むので、
構造体のフィールドと一致するパターンには省略法があります: 構造体のフィールドの名前を列挙するだけで、
パターンから生成される変数は同じ名前になるのです。リスト18-13は、リスト18-12と同じ振る舞いをするコードを表示していますが、
let
パターンで生成される変数はa
とb
ではなく、x
とy
です。
ファイル名: 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); }
このコードは、p
変数のx
とy
フィールドに一致する変数x
とy
を生成します。
結果は、変数x
とy
がp
構造体の値を含むというものです。
また、全フィールドに対して変数を生成するのではなく、リテラル値を構造体パターンの一部にして分配することもできます。 そうすることで他のフィールドは分配して変数を生成しつつ、一部のフィールドは特定の値と一致するか確認できます。
リスト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), } }
最初のアームは、y
フィールドの値がリテラル0
と一致するならマッチすると指定することで、x
軸上にあるどんな点とも一致します。
このパターンはそれでも、このアームのコードで使用できるx
変数を生成します。
同様に、2番目のアームは、x
フィールドが0
ならマッチすると指定することでy
軸上のどんな点とも一致し、
y
フィールドの値には変数y
を生成します。3番目のアームは何もリテラルを指定しないので、
それ以外のあらゆるPoint
に合致し、x
とy
フィールド両方に変数を生成します。
この例で、値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!( // 色を赤{}, 緑{}, 青{}に変更 "Change the color to red {}, green {}, and blue {}", r, g, b ) } } }
このコードは、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
インスタンスへの参照を走査し、x
とy
値に簡単に計算を行えるように、
参照と構造体を分配します。
# #![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(); #}
このコードは、値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); }
このコードは、最初の引数として渡された値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); #}
このコードは、Can't overwrite an existing customized value
、そしてsetting is Some(5)
と出力するでしょう。
最初のマッチアームで、どちらのSome
列挙子内部の値にも合致させたり、使用する必要はありませんが、
setting_value
とnew_setting_value
がSome
列挙子の場合を確かに確認する必要があります。
その場合、何故setting_value
を変更しないかを出力し、変更しません。
2番目のアームの_
パターンで表現される他のあらゆる場合(setting_value
とnew_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) }, } #}
このコードは、Some numbers: 2, 8, 32
と出力し、値4と16は無視されます。
名前を_
で始めて未使用の変数を無視する
変数を作っているのにどこでも使用していなければ、バグかもしれないのでコンパイラは通常、警告を発します。 しかし時として、まだ使用しない変数を作るのが有用なこともあります。プロトタイプを開発していたり、 プロジェクトを始めた直後だったりなどです。このような場面では、変数名をアンダースコアで始めることで、 コンパイラに未使用変数について警告しないよう指示することができます。リスト18-20で2つの未使用変数を生成していますが、 このコードを実行すると、そのうちの1つにしか警告が出ないはずです。
ファイル名: src/main.rs
fn main() { let _x = 5; let y = 10; }
ここで、変数y
を使用していないことに対して警告が出ていますが、アンダースコアが接頭辞になっている変数には、
使用していないという警告が出ていません。
_
だけを使うのとアンダースコアで始まる名前を使うことには微妙な違いがあることに注意してください。
_x
記法はそれでも、値を変数に束縛する一方で、_
は全く束縛しません。この差異が問題になる場合を示すために、
リスト18-21はエラーを提示するでしょう。
// こんにちは!
let s = Some(String::from("Hello!"));
if let Some(_s) = s {
// 文字列が見つかりました
println!("found a string");
}
println!("{:?}", s);
それでも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); #}
このコードは、s
を何にも束縛しないので、ただ単に上手く動きます。つまり、ムーブされないのです。
..
で値の残りの部分を無視する
多くの部分がある値では、..
記法を使用していくつかの部分だけを使用して残りを無視し、
無視する値それぞれにアンダースコアを列挙する必要性を回避できます。..
パターンは、
パターンの残りで明示的にマッチさせていない値のどんな部分も無視します。リスト18-23では、
3次元空間で座標を保持するPoint
構造体があります。match
式でx
座標のみ処理し、
y
とz
フィールドの値は無視したいです。
# #![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), } #}
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); }, } }
このコードにおいて、最初と最後の値はfirst
とlast
に合致します。..
は、
途中のもの全部に合致し、無視します。
しかしながら、..
を使うのは明確でなければなりません。どの値がマッチしてどの値が無視されるべきかが不明瞭なら、
コンパイラはエラーを出します。リスト18-25は、..
を曖昧に使用する例なので、コンパイルできません。
ファイル名: src/main.rs
fn main() {
let numbers = (2, 4, 8, 16, 32);
match numbers {
(.., second, ..) => {
println!("Some numbers: {}", second)
},
}
}
この例をコンパイルすると、こんなエラーが出ます:
error: `..` can only be used once per tuple or tuple struct pattern
(エラー: `..`は、タプルやタプル構造体パターン1つにつき、1回しか使用できません)
--> src/main.rs:5:22
|
5 | (.., second, ..) => {
| ^^
コンパイラが、second
の値に合致する前にタプルの幾つの値を無視し、それからそれによってさらに幾つの値を無視するかを決めることは不可能です。
このコードは、2
を無視し、second
に4
を束縛し、それから8
、16
、32
を無視したり、
2
と4
を無視してsecond
に8
を束縛し、それから16
と32
を無視するなどを意味することもあるでしょう。
変数名のsecond
は、コンパイラにとってなんの特別な意味もなく、このように2箇所で..
を使うのは曖昧なので、
コンパイルエラーになります。
ref
とref 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);
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); #}
robot_name
のSome
列挙子の値が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); #}
この例はコンパイルが通り、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 => (), } #}
この例は、less than five: 4
と出力します。num
が最初のアームのパターンと比較されると、
Some(4)
はSome(x)
に一致するので、マッチします。そして、マッチガードがx
の値が5
未満か確認し、
そうなっているので、最初のアームが選択されます。
代わりにnum
がSome(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); }
このコードは今度は、Default case, x = Some(5)
と出力するでしょう。2番目のマッチアームのパターンは、
外側のy
を覆い隠してしまう新しい変数y
を導入せず、マッチガード内で外側のy
を使用できることを意味します。
外側のy
を覆い隠してしまうSome(y)
としてパターンを指定するのではなく、Some(n)
を指定しています。
これにより、何も覆い隠さない新しい変数n
が生成されます。match
の外側にはn
変数は存在しないからです。
マッチガードのif n == y
はパターンではなく、故に新しい変数を導入しません。このy
は、
新しいシャドーイングされたy
ではなく、外側のy
であり、n
とy
を比較することで、
外側のy
と同じ値を探すことができます。
また、マッチガードでor演算子の|
を使用して複数のパターンを指定することもできます;
マッチガードの条件は全てのパターンに適用されます。リスト18-31は、
|
を使用するパターンとマッチガードを組み合わせる優先度を示しています。この例で重要な部分は、
if y
は6
にしか適用されないように見えるのに、if y
マッチガードが4
、5
、そして6
に適用されることです。
# #![allow(unused_variables)] #fn main() { let x = 4; let y = false; match x { // はい 4 | 5 | 6 if y => println!("yes"), // いいえ _ => println!("no"), } #}
マッチの条件は、x
の値が4
、5
、6
に等しくかつy
がtrue
の場合だけにアームがマッチすると宣言しています。
このコードが走ると、最初のアームのパターンはx
が4
なので、合致しますが、マッチガードif y
は偽なので、
最初のアームは選ばれません。コードは2番目のアームに移動して、これがマッチし、このプログラムはno
と出力します。
理由は、if
条件が最後の値の6
だけでなく、パターン全体4 | 5 | 6
に適用されるからです。
言い換えると、パターンと関わるマッチガードの優先度は、以下のように振る舞います:
(4 | 5 | 6) if y => ...
以下のようにではありません:
4 | 5 | (6 if y) => ...
コードを実行後には、優先度の動作は明らかになります: マッチガードが|
演算子で指定される値のリストの最後の値にしか適用されないなら、
アームはマッチし、プログラムはyes
と出力したでしょう。
@
束縛
at演算子(@
)により、値を保持する変数を生成するのと同時にその値がパターンに一致するかを調べることができます。
リスト18-32は、Message::Hello
のid
フィールドが範囲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の多彩な機能の高度な視点に目を向けます。