Rust のパターン構文の練習

RustRustBeginner
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

パターン構文へようこそ。この実験はRust ブックの一部です。LabEx で Rust のスキルを練習することができます。

この実験では、パターンにおける有効な構文について説明し、それぞれの構文をいつ、なぜ使うのかの例を示します。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/DataTypesGroup -.-> rust/boolean_type("Boolean Type") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") subgraph Lab Skills rust/variable_declarations -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/mutable_variables -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/integer_types -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/boolean_type -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/string_type -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/function_syntax -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/expressions_statements -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/lifetime_specifiers -.-> lab-100446{{"Rust のパターン構文の練習"}} rust/method_syntax -.-> lab-100446{{"Rust のパターン構文の練習"}} end

パターン構文

このセクションでは、パターンにおける有効な構文をすべてまとめ、それぞれの構文をいつ、なぜ使うのかについて説明します。

リテラルのマッチング

第 6 章で見たように、パターンを直接リテラルとマッチさせることができます。次のコードはいくつかの例を示しています。

ファイル名: src/main.rs

let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

このコードは、x の値が 1 であるため、one を出力します。この構文は、特定の具体的な値を取得した場合にコードが何らかのアクションを実行する場合に便利です。

名前付き変数のマッチング

名前付き変数は、あらゆる値にマッチする反駁不能パターンであり、この本で何度も使用してきました。ただし、match 式の中で名前付き変数を使用すると、少し複雑な状況が生じます。match は新しいスコープを開始するため、match 式内のパターンの一部として宣言された変数は、match 構文の外で同じ名前の変数をシャドウイングします。これはすべての変数に当てはまります。リスト 18-11 では、値が Some(5)x という名前の変数と、値が 10y という名前の変数を宣言しています。そして、x の値に対して match 式を作成しています。マッチアームのパターンと最後の println! を見て、このコードを実行する前、またはこれ以降を読む前に、コードが何を出力するかを考えてみてください。

ファイル名: src/main.rs

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

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

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

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

match 式が実行されたときに何が起こるかを見ていきましょう。最初のマッチアーム [3] のパターンは、定義された x の値 [1] とマッチしないため、コードは続行されます。

2 番目のマッチアーム [4] のパターンは、Some 値の中のあらゆる値にマッチする y という名前の新しい変数を導入します。match 式の中は新しいスコープなので、これは最初に値 10 で宣言した y [2] ではなく、新しい y 変数です。この新しい y バインディングは、Some の中のあらゆる値にマッチします。これは x に含まれるものです。したがって、この新しい yxSome の内側の値にバインドされます。その値は 5 なので、そのアームの式が実行され、Matched, y = 5 が出力されます。

もし xSome(5) ではなく None 値だった場合、最初の 2 つのアームのパターンはマッチしないため、値はアンダースコア [5] にマッチします。アンダースコアアームのパターンでは x 変数を導入していないため、式の中の x は依然としてシャドウイングされていない外側の x です。この仮定の場合、matchDefault case, x = None を出力します。

match 式が終了すると、そのスコープも終了し、内側の y のスコープも終了します。最後の println! [6] は at the end: x = Some(5), y = 10 を出力します。

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

複数のパターン

match 式では、| 構文を使用して複数のパターンをマッチさせることができます。これはパターンの「または」演算子です。たとえば、次のコードでは x の値をマッチアームとマッチさせています。最初のアームには「または」オプションがあり、つまり x の値がそのアーム内のいずれかの値とマッチする場合、そのアームのコードが実行されます。

ファイル名: src/main.rs

let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

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

..= を使った値の範囲のマッチング

..= 構文を使用すると、包括的な値の範囲にマッチさせることができます。次のコードでは、パターンが指定された範囲内のいずれかの値とマッチすると、そのアームが実行されます。

ファイル名: src/main.rs

let x = 5;

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}

x1234、または 5 の場合、最初のアームがマッチします。この構文は、同じ考えを | 演算子を使って表現するよりも、複数のマッチ値に対して便利です。| を使う場合、1 | 2 | 3 | 4 | 5 と指定する必要があります。範囲を指定する方がはるかに短くなります。特に、1 から 1000 までの任意の数値にマッチさせたい場合などでは便利です。

コンパイラはコンパイル時に範囲が空でないことをチェックします。Rust が範囲が空かどうかを判断できる型は char と数値型のみなので、範囲は数値型または char 型の値でのみ使用できます。

以下は char 型の値の範囲を使用した例です。

ファイル名: src/main.rs

let x = 'c';

match x {
    'a'..='j' => println!("early ASCII letter"),
    'k'..='z' => println!("late ASCII letter"),
    _ => println!("something else"),
}

Rust は 'c' が最初のパターンの範囲内であることを判断し、early ASCII letter を出力します。

値を分解するためのデストラクチャリング

パターンを使用して、構造体、列挙型、タプルを分解し、これらの値の異なる部分を使用することもできます。それでは、それぞれの値について見ていきましょう。

構造体のデストラクチャリング

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

ファイル名: 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; と書くと重複が多くなるため、Rust には構造体フィールドにマッチするパターンの省略記法があります。構造体フィールドの名前を列挙するだけで、パターンから作成される変数は同じ名前になります。リスト 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 を作成します。結果として、変数 xy には p 構造体の値が含まれます。

また、すべてのフィールドに変数を作成するのではなく、構造体パターンの一部としてリテラル値を使ってデストラクチャリングすることもできます。これにより、一部のフィールドが特定の値に一致するかどうかをテストしながら、他のフィールドを分解する変数を作成することができます。

リスト 18-14 では、Point 値を 3 つのケースに分ける match 式があります。x 軸上にある点(y = 0 の場合)、y 軸上にある点(x = 0 の場合)、どちらの軸上にもない点です。

ファイル名: src/main.rs

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

    match p {
        Point { x, y: 0 } => println!("On the x axis at {x}"),
        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 の両方のフィールドに対する変数を作成します。

この例では、値 px0 であるため 2 番目のアームにマッチするので、このコードは On the y axis at 7 を出力します。

match 式は最初にマッチするパターンを見つけるとアームのチェックを停止することを忘れないでください。したがって、Point { x: 0, y: 0}x 軸と y 軸の両方にありますが、このコードは On the x axis at 0 のみを出力します。

列挙型のデストラクチャリング

この本では列挙型をデストラクチャリングしてきました(たとえば、リスト 6-5)。しかし、列挙型をデストラクチャリングするパターンが、列挙型内に格納されているデータの定義方法に対応していることについては、まだ明示的に説明していません。例として、リスト 18-15 ではリスト 6-2 の Message 列挙型を使用し、各内部値をデストラクチャリングするパターンを持つ match 式を記述しています。

ファイル名: src/main.rs

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

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

    match msg {
      2 Message::Quit => {
            println!(
                "The Quit variant has no data to destructure."
            );
        }
      3 Message::Move { x, y } => {
            println!(
                "Move in the x dir {x}, in the y dir {y}"
            );
        }
      4 Message::Write(text) => {
            println!("Text message: {text}");
        }
      5 Message::ChangeColor(r, g, b) => println!(
            "Change color to red {r}, green {g}, and blue {b}"
        ),
    }
}

リスト 18-15: さまざまな種類の値を保持する列挙型のバリアントをデストラクチャリングする

このコードは Change color to red 0, green 160, and blue 255 を出力します。msg [1] の値を変更して、他のアームのコードが実行されるのを確認してみてください。

Message::Quit [2] のようにデータを持たない列挙型のバリアントの場合、値をさらに分解することはできません。リテラルの Message::Quit 値にのみマッチさせることができ、そのパターンには変数がありません。

Message::Move [3] のような構造体のような列挙型のバリアントの場合、構造体にマッチさせるために指定するパターンと同様のパターンを使用できます。バリアント名の後に中括弧を置き、その中に変数付きのフィールドを列挙することで、このアームのコードで使用するために各部品を分解します。ここでは、リスト 18-13 で行ったように省略記法を使用しています。

Message::Write のように 1 つの要素を持つタプルを保持する [4] や、Message::ChangeColor のように 3 つの要素を持つタプルを保持する [5] タプルのような列挙型のバリアントの場合、パターンはタプルにマッチさせるために指定するパターンと似ています。パターン内の変数の数は、マッチさせるバリアント内の要素の数と一致する必要があります。

ネストされた構造体と列挙型のデストラクチャリング

これまでの例では、構造体や列挙型を 1 レベル深さでマッチさせてきましたが、マッチングはネストされた要素にも適用できます! たとえば、リスト 18-15 のコードをリファクタリングして、ChangeColor メッセージで RGB と HSV の色をサポートすることができます。これをリスト 18-16 に示します。

ファイル名: src/main.rs

enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

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

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

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => println!(
            "Change color to red {r}, green {g}, and blue {b}"
        ),
        Message::ChangeColor(Color::Hsv(h, s, v)) => println!(
            "Change color to hue {h}, saturation {s}, value {v}"
        ),
        _ => (),
    }
}

リスト 18-16: ネストされた列挙型に対するマッチング

match 式の最初のアームのパターンは、Color::Rgb バリアントを含む Message::ChangeColor 列挙型のバリアントにマッチします。その後、パターンは内部の 3 つの i32 値にバインドされます。2 番目のアームのパターンも Message::ChangeColor 列挙型のバリアントにマッチしますが、内部の列挙型は Color::Hsv にマッチします。2 つの列挙型が関与している場合でも、これらの複雑な条件を 1 つの match 式で指定することができます。

構造体とタプルのデストラクチャリング

デストラクチャリングパターンをさらに複雑な方法で組み合わせ、ネストすることができます。次の例は、構造体とタプルをタプルの中にネストし、すべてのプリミティブ値を分解する複雑なデストラクチャリングを示しています。

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) {
    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 を出力します。

ほとんどの場合、特定の関数パラメータが不要になったときは、シグネチャを変更して未使用のパラメータを含まないようにします。たとえば、特定の型シグネチャが必要なトレイトを実装する場合で、実装内の関数本体があるパラメータを必要としない場合、関数パラメータを無視することは特に便利です。そうすることで、名前を使用した場合に発生する未使用の関数パラメータに関するコンパイラの警告を回避できます。

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

他のパターン内で _ を使用して、値の一部のみを無視することもできます。たとえば、値の一部のみをテストしたいが、対応する実行コードで他の部分を使用する必要がない場合です。リスト 18-18 は、設定値を管理するコードを示しています。ビジネス要件は、ユーザーが既存の設定のカスタマイズを上書きできないようにする一方で、設定が現在未設定の場合には設定を解除して値を設定できるようにすることです。

ファイル名: src/main.rs

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 を変更しない理由を出力し、setting_value は変更されません。

2 番目のアームの _ パターンで表される他のすべてのケース(setting_value または new_setting_valueNone の場合)では、new_setting_valuesetting_value に設定できるようにします。

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

ファイル名: src/main.rs

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 を出力し、値 416 は無視されます。

アンダースコアで始まる名前の未使用変数

変数を作成したが、どこでもその変数を使用しない場合、Rust は通常警告を発行します。なぜなら、未使用の変数はバグの原因になる可能性があるからです。ただし、プロトタイピングやプロジェクトの開始時など、まだ使用しない変数を作成することが役立つ場合もあります。このような状況では、変数名をアンダースコアで始めることで、Rust に未使用変数に関する警告を出さないように指示することができます。リスト 18 - 20 では、2 つの未使用変数を作成していますが、このコードをコンパイルすると、片方の変数に関する警告のみが表示されます。

ファイル名: src/main.rs

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

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

ここでは、変数 y を使用していないという警告が表示されますが、_x を使用していないという警告は表示されません。

ただ、単に _ を使用する場合とアンダースコアで始まる名前を使用する場合には微妙な違いがあります。構文 _x は依然として値を変数に束縛しますが、_ はまったく束縛しません。この違いが重要になるケースを示すために、リスト 18 - 21 ではエラーが発生します。

ファイル名: src/main.rs

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

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

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

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

s の値は _s にムーブされるため、s を再度使用できなくなり、エラーが発生します。ただし、単にアンダースコアを使用する場合は、値を束縛することはありません。リスト 18 - 22 は、s_ にムーブされないため、エラーなくコンパイルされます。

ファイル名: src/main.rs

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 座標のみを操作し、y および z フィールドの値を無視したいとします。

ファイル名: src/main.rs

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 の値を列挙し、その後に .. パターンを含めます。これは、特に関連するフィールドが 1 つまたは 2 つだけの多くのフィールドを持つ構造体を扱う場合に、y: _z: _ を列挙するよりも迅速です。

.. 構文は、必要なだけの値に展開されます。リスト 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 にマッチします。.. は中間のすべての値をマッチさせて無視します。

ただし、.. の使用は明確でなければなりません。どの値がマッチさせる対象で、どの値を無視するかが不明確な場合、Rust はエラーを返します。リスト 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 pattern
 --> src/main.rs:5:22
  |
5 |         (.., second,..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

Rust は、値を second にマッチさせる前にタプル内のどれだけの値を無視し、その後さらにどれだけの値を無視するかを判断することができません。このコードは、2 を無視し、second4 に束縛し、その後 81632 を無視することを意味する場合もあれば、24 を無視し、second8 に束縛し、その後 1632 を無視することを意味する場合もあります。変数名 second は Rust にとって特別な意味を持たないため、このように 2 か所で .. を使用すると曖昧になるため、コンパイラエラーが発生します。

マッチガードによる追加の条件式

マッチガード は、match アームのパターンの後に指定される追加の if 条件で、そのアームが選択されるためにはこの条件も満たさなければなりません。マッチガードは、パターンだけでは表現できないより複雑な条件を表現するのに便利です。

この条件式は、パターン内で作成された変数を使用することができます。リスト 18 - 26 は、最初のアームに Some(x) というパターンがあり、さらに if x % 2 == 0 というマッチガードがある match の例です(この条件は、数値が偶数の場合に true になります)。

ファイル名: src/main.rs

let num = Some(4);

match num {
    Some(x) if x % 2 == 0 => println!("The number {x} is even"),
    Some(x) => println!("The number {x} is odd"),
    None => (),
}

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

この例では、The number 4 is even と出力されます。num が最初のアームのパターンと比較されるとき、Some(4)Some(x) にマッチするため、マッチします。次に、マッチガードが x を 2 で割った余りが 0 かどうかを確認し、0 であるため、最初のアームが選択されます。

もし numSome(5) だった場合、最初のアームのマッチガードは false になります。なぜなら、5 を 2 で割った余りは 1 で、0 と等しくないからです。その場合、Rust は 2 番目のアームに進み、2 番目のアームにはマッチガードがないため、任意の Some バリアントにマッチします。

if x % 2 == 0 という条件をパターン内で表現することはできません。そのため、マッチガードを使用することでこのロジックを表現することができます。ただし、この追加の表現力には欠点もあります。マッチガード式が含まれる場合、コンパイラは網羅性のチェックを行いません。

リスト 18 - 11 では、マッチガードを使用してパターンのシャドウイングの問題を解決できることを述べました。match 式のパターン内で新しい変数を作成し、match の外の変数を使用しなかったことを思い出してください。その新しい変数のため、外側の変数の値と比較することができませんでした。リスト 18 - 27 は、マッチガードを使用してこの問題を解決する方法を示しています。

ファイル名: 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 = {y}", x);
}

リスト 18 - 27: 外側の変数との等価性をテストするためにマッチガードを使用する

このコードは、Default case, x = Some(5) と出力します。2 番目のマッチアームのパターンは、外側の y をシャドウする新しい変数 y を導入しません。つまり、マッチガードで外側の y を使用することができます。外側の y をシャドウする Some(y) というパターンを指定する代わりに、Some(n) を指定しています。これにより、match の外に n 変数がないため、何もシャドウしない新しい変数 n が作成されます。

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

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

ファイル名: src/main.rs

let x = 4;
let y = false;

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

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

このマッチ条件は、x の値が 45、または 6 であり、かつ ytrue の場合にのみアームがマッチすることを示しています。このコードが実行されると、最初のアームのパターンは x4 であるためマッチしますが、マッチガード if yfalse であるため、最初のアームは選択されません。コードは 2 番目のアームに進み、このアームはマッチするため、このプログラムは no と出力します。理由は、if 条件は 4 | 5 | 6 という全体のパターンに適用され、最後の値 6 にのみ適用されるわけではないからです。言い換えると、パターンに対するマッチガードの優先順位は次のように動作します。

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

次のようにではなく:

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

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

@ バインディング

アット演算子 @ を使用すると、値をパターンマッチングでテストすると同時に、その値を保持する変数を作成することができます。リスト 18 - 29 では、Message::Helloid フィールドが 3..=7 の範囲内にあることをテストしたいとします。また、その値を変数 id_variable に束縛して、アームに関連付けられたコードで使用できるようにしたいとします。この変数を id(フィールド名と同じ)と名付けることもできますが、この例では別の名前を使用します。

ファイル名: src/main.rs

enum Message {
    Hello { id: i32 },
}

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

match msg {
    Message::Hello {
        id: id_variable @ 3..=7,
    } => println!("Found an id in range: {id_variable}"),
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    }
    Message::Hello { id } => println!("Some other id: {id}"),
}

リスト 18 - 29: @ を使用してパターン内の値をテストしながら変数に束縛する

この例では、Found an id in range: 5 と出力されます。範囲 3..=7 の前に id_variable @ を指定することで、範囲にマッチした値をキャプチャすると同時に、その値が範囲パターンにマッチすることをテストしています。

2 番目のアームでは、パターンに範囲のみを指定しているため、アームに関連付けられたコードには id フィールドの実際の値を含む変数がありません。id フィールドの値は 10、11、または 12 である可能性がありますが、そのパターンに関連付けられたコードはどれであるかを知りません。パターンコードは id フィールドの値を使用することができません。なぜなら、id の値を変数に保存していないからです。

最後のアームでは、範囲を指定せずに変数を指定しているため、アームのコードで id という名前の変数を使用して値を利用できます。これは、構造体フィールドの省略記法を使用しているためです。ただし、このアームでは、最初の 2 つのアームのように id フィールドの値に対するテストを適用していません。つまり、どんな値でもこのパターンにマッチします。

@ を使用することで、1 つのパターン内で値をテストし、変数に保存することができます。

まとめ

おめでとうございます!パターン構文の実験を完了しました。LabEx でさらに多くの実験を行い、スキルを向上させることができます。