Chapter 22

パターンマッチと条件分岐

論駁可能なパターン

今までに出てきたパターンは,全て「必ず成功する」パターンでした.

let (x, y) = (1, 2);
// イコールの右側に要素数 2 のタプルがあれば
// パターンマッチは必ずうまくいく

一方,「必ず成功するとは限らない」パターンがあります.その一つがスライスパターンです.

たとえば, [x, y, z] がスライスパターンです.長さ 3 のスライスが与えられれば,その各要素が x y z に代入されます.一方,長さが 3 でないスライスが与えられると,パターンマッチは失敗します. [T] という型自体には長さの情報が含まれないため,型からスライスの長さが 3 であるか判断することはできません.

このように,失敗する可能性のあるパターンを,論駁可能なパターンといいます.これに対し,今までの「必ず成功する」パターンを論駁不可能なパターンといいます.

この章では,論駁可能なパターンの使い方を紹介します.

if let

論駁可能なパターンを使う状況の一つに, if letがあります.

fn main() {
    let ref_slice: &[i32] = &[10, 15, 20];
    if let [x, y, z] = *ref_slice {
        println!("{} {} {}", x, y, z);
    } else {
        println!("マッチに失敗しました");
    }
}

if の直後に, let 文と同じ形式で let [x, y, z] = *ref_slice と書かれています. = の右がスライスになっています(スライス型の変数を作ることはできないので,一旦スライス参照型の変数 ref_slice を作ってから参照外ししています).

こう書くと,スライス *ref_slice[x, y, z] にマッチするとき,すなわち *ref_slice の長さがちょうど 3 であるときのみ,パターンマッチが行われてブロックの中身が実行されます.

今回は *ref_slice の長さが 3 なので, xyz の値がそれぞれ 10, 15, 20 になってブロックが実行されます.よって出力は 10 15 20 となります.

一方, *ref_slice の長さが 3 以外だと

fn main() {
    let ref_slice: &[i32] = &[10, 15]; // 長さ 2
    if let [x, y, z] = *ref_slice {
        println!("{} {} {}", x, y, z);
    } else {
        println!("マッチに失敗しました");
    }
}

if ではなく else の後のブロックが実行されて, マッチに失敗しました と出力されます.

様々なパターン

リテラルパターン

論駁可能なパターンの中では,リテラルを書くことができます.

use proconio::input;

fn main() {
    input! {
        vector: [(i32, i32); 5],
    }
    for &tuple in &vector {
        if let (1, value) = tuple {
            println!("{}", value);
        } else if let (2, value) = tuple {
            println!("{}", value * value);
        } else if let (0, 0) = tuple {
            break;
        } else {
            println!("?");
        }
    }
}

入力としてタプルのベクタを受け取り, for 式で走査しています.

  • タプルの最初の要素が 1 のとき,タプルはパターン (1, value) にマッチします.
  • タプルの最初の要素が 2 のとき,タプルはパターン (2, value) にマッチします.
  • タプルの要素がどちらも 0 のとき,タプルはパターン (0, 0) にマッチします.

このプログラムに入力として

標準入力
1 5
2 4
5 2
0 0
1 2

を与えると,

  1. タプル (1, 5)(1, value) にマッチするので, value の値がそのまま出力される.
  2. タプル (2, 4)(2, value) にマッチするので, value の値の 2 乗が出力される.
  3. タプル (5, 2) はいずれにもマッチしないので, ? と出力される.
  4. タプル (0, 0)(0, 0) にマッチするので,ループが終了する.

となって全体では

標準出力
5
16
?

と出力されます.

このコードは後述する match 式を使うと綺麗に書き直せます.

複数のパターン

パターンは, | でつないで複数個書くことができます.

fn main() {
    let array = [(1, 92), (3, 91), (95, 1), (94, 2)];
    let mut vector = Vec::new();
    for tuple in &array {
        if let (1, value) | (value, 2) = *tuple {
            vector.push(value);
        }
    }
    assert_eq!(vector, vec![92, 94]);
}

if let の後に, (1, value)(value, 2) という 2 つのパターンを書いています.

  • (1, value) にマッチすれば, value にタプルの 2 つめの要素が代入されてブロックの中身が実行されます.
  • (value, 2) にマッチすれば, value にタプルの 1 つめの要素が代入されてブロックの中身が実行されます.
  • どちらにもマッチしなければ,ブロックの中身は実行されません.

複数のパターンの間で使われている変数が一致していなかった場合エラーになります.

fn main() {
    let tuple = (3, 2, 1);
    if let (x, 0, 0) | (x, y, 1) | (x, y, 2) = tuple {
        println!("{} {}", x, y); // y は使える?使えない?
    }
}

(x, y, 1)(x, y, 2) の中には変数 y が現れているのに, (x, 0, 0) の中には現れていないので,エラーになります.

パターンの間で変数の型が一致していないときもエラーになります.

fn main() {
    let tuple: (i32, f64) = (1, 2.0);
    if let (1, x) | (x, 2.0) = tuple {
        println!("{}", x); // x は i32 ? f64 ?
    }
}

範囲パターン

..= を用いて,ある範囲にマッチするパターンを書くことができます.

fn main() {
    let tuple = (1, 2);
    if let (0..=5, x) = tuple {
        assert_eq!(x, 2);
    } else {
        panic!();
    }
}

(0..=5, x) は,一つ目の要素が 0 以上 5 以下であるようなタプルにマッチします. a..=b において a > b のときはコンパイルエラーになります.

ワイルドカードパターン

fn main() {
    let tuple = (3, 1, 2);
    if let (1, x, y) | (x, 1, y) | (x, y, 1) = tuple {
        println!("少なくとも 1 つが 1");
    }
}

if ブロックの中身は, (1, x, y) (x, 1, y) (x, y, 1) のいずれかにマッチするとき,すなわち 3 つの要素のうち少なくとも 1 つが 1 であるようなときに実行されます.

しかし,ブロックの中で x y を使う必要が無ければ,パターンの中の x y は無駄になってしまいます.このような場合は, x y_ で置き換えます.

fn main() {
    let tuple = (3, 1, 2);
    if let (1, _, _) | (_, 1, _) | (_, _, 1) = tuple {
        println!("少なくとも 1 つが 1");
    }
}

_ に対して代入された値は捨てられ,ブロックの中で使うことができません. _ワイルドカードパターンといいます.

変数への代入は「決められた型の値をメモに書き込む」イメージですが,ワイルドカードパターンへの代入は「どんな型の値も書き込めるが,書いた内容が次々と消えてゆく」イメージです.

fn main() {
    let x = 10;
    let _ = x; // 意味のない代入文
}

ワイルドカードパターンは, for 式で繰り返しの回数だけを指定するときにも使えます.

fn main() {
    for _ in 0..4 {
        println!("Knock, knock, knockin' on heaven's door");
    }
}

Knock, knock, knockin' on heaven's door と 4 回出力します.

レストパターン

次のコードを見てください.

fn main() {
    let ref_slice: &[i32] = &[10, 20, 30];
    if let [first, second, ..] = *ref_slice {
        println!("最初の要素: {}", first);
        println!("2 番目の要素: {}", second);
    } else {
        println!("長さ 1 以下のスライスです");
    }
}

[first, second, ..] というパターンは,スライスの長さが 2 以上であればマッチに成功し, first second にそれぞれ最初の要素と次の要素が代入されます.一方,スライスの長さが 1 以下であればマッチに失敗します.

このように,「残りの要素」を表す ..レストパターンといいます.

レストパターンは,スライスパターンの途中や

fn main() {
    let ref_slice: &[i32] = &[10, 20, 30];
    if let &[first, .., last] = ref_slice {
        assert_eq!(first, 10);
        assert_eq!(last, 30);
    } else {
        panic!();
    }
}

最初でも使えます.

fn main() {
    let ref_slice: &[i32] = &[10, 20, 30];
    if let &[.., second_to_last, last] = ref_slice {
        assert_eq!(second_to_last, 20);
        assert_eq!(last, 30);
    } else {
        panic!();
    }
}

while let

if let 式は「マッチしたらブロックの中身が実行される」ものでした.一方, while let 式は「マッチしている間ブロックの中身が実行され続ける」ものです.

fn main() {
    let array = [0, 0, 0, 1, 2];
    let mut ref_slice = &array[..];
    while let [0, ..] = *ref_slice {
        ref_slice = &ref_slice[1..];
    }
    assert_eq!(ref_slice, [1, 2]);
}
  1. はじめ ref_slicearray の全体 [0, 0, 0, 1, 2] のスライスへの参照です.
  2. [0, 0, 0, 1, 2][0, ..] にマッチするので,ブロックが実行されます.
    1. ref_slice[1..]array[0, 0, 1, 2] の範囲を指すスライスです.これが ref_slice に代入されます.
  3. [0, 0, 1, 2][0, ..] にマッチするので,ブロックが実行されます.
    1. ref_slice[1..]array[0, 1, 2] の範囲を指すスライスです.これが ref_slice に代入されます.
  4. [0, 1, 2][0, ..] にマッチするので,ブロックが実行されます.
    1. ref_slice[1..]array[1, 2] の範囲を指すスライスです.これが ref_slice に代入されます.
  5. [1, 2][0, ..] にマッチしないので,ブロックが実行されずにループが終了します.

match

これは,で登場したコードです.

use proconio::input;

fn main() {
    input! {
        vector: [(i32, i32); 5],
    }
    for &tuple in &vector {
        if let (1, value) = tuple {
            println!("{}", value);
        } else if let (2, value) = tuple {
            println!("{}", value * value);
        } else if let (0, 0) = tuple {
            break;
        } else {
            println!("?");
        }
    }
}

3 つのパターン (1, value) (2, value) (0, 0) について, tuple がマッチするかどうか順に調べています.

このように,一つの値といくつかのパターンについてマッチするか順に調べていくような場合は, matchを使うことで次のように書けます.

use proconio::input;

fn main() {
    input! {
        vector: [(i32, i32); 5],
    }
    for &tuple in &vector {
        match tuple {
            (1, value) => println!("{}", value),
            (2, value) => println!("{}", value * value),
            (0, 0) => break,
            _ => println!("?"),
        }
    }
}

match の直後に式 tuple が置かれ,その後の { } で囲まれた部分に

パターン =>,

が並んでいます. match 式内の各パターンをアームといいます.

match 式では,上から順にパターンマッチが成功するか確かめられ,マッチしたアームについて式の内容が実行されます.

式の終わりがセミコロン ; ではなくカンマ , であることに注意してください.

順序

match 式のアームは,上から順にマッチするか調べられます.よって,

use proconio::input;

fn main() {
    input! {
        tuple: (i32, i32),
    }
    match tuple {
        (1, 1) => println!("どちらも 1"),
        (1, _) | (_, 1) => println!("片方のみが 1"),
        _ => println!("どちらも 1 ではない"),
    }
}

このコードでは,タプルが (1, 1) のとき, (1, _)(_, 1) ではなく (1, 1) にマッチします.一方,順番を入れ替えて

use proconio::input;

fn main() {
    input! {
        tuple: (i32, i32),
    }
    match tuple {
        (1, _) | (_, 1) => println!("片方のみが 1"),
        (1, 1) => println!("どちらも 1"),
        _ => println!("どちらも 1 ではない"),
    }
}

とすると,たとえタプルが (1, 1) であっても (1, _) の方にマッチしてしまいます.

カンマ , の省略

=> の後がブロックであるような場合は,カンマ , を省略できます.

use proconio::input;

fn main() {
    loop {
        input! {
            x: i32,
        }
        match x {
            0 => {
                break;
            }
            n => {
                for _ in 0..n {
                    print!("!");
                }
                println!();
            }
        }
    }
}

値を返す match

match 式も値を返すことができます.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let y = match x {
        0 => 1,
        1 => 0,
        _ => {
            println!("neither 0 nor 1");
            0
        }
    };
    println!("{}", y);
}

unreachable!()

match 式のアームは,全ての場合をカバーしなければなりません.例えば,上の例で最後のアームを消すと

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let y = match x {
        0 => 1,
        1 => 0,
    };
    // x が 0 でも 1 でもないとき,
    // y の値は何になるのか?
    println!("{}", y);
}

エラーになります.

error[E0004]: non-exhaustive patterns: `i32::MIN..=-1_i32` and `2_i32..=i32::MAX` not covered
 --> src/main.rs:7:19
  |
7 |     let y = match x {
  |                   ^ patterns `i32::MIN..=-1_i32` and `2_i32..=i32::MAX` not covered
  |
  = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
  = note: the matched value is of type `i32`

non-exhaustive patterns は「網羅的でないパターン」という意味です.

これは,理論上他の場合が存在しないという場合であっても起こることがあります.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    match x % 3 {
        0 => println!("3 の倍数"),
        1 | -2 => println!("1 を引くと 3 の倍数"),
        2 | -1 => println!("1 を足すと 3 の倍数"),
    }
}

x % 3 という式が -3 以下の値や 3 以上の値になることはありません.しかし,自動的にそのような判定がなされることは無く,エラーになってしまいます.

error[E0004]: non-exhaustive patterns: `i32::MIN..=-3_i32` and `3_i32..=i32::MAX` not covered
 --> src/main.rs:7:11
  |
7 |     match x % 3 {
  |           ^^^^^ patterns `i32::MIN..=-3_i32` and `3_i32..=i32::MAX` not covered
  |
  = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms
  = note: the matched value is of type `i32`

このような場合は,最後にワイルドカードパターンを追加し, unreachable!() という式を書きます.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    match x % 3 {
        0 => println!("3 の倍数"),
        1 | -2 => println!("1 を引くと 3 の倍数"),
        2 | -1 => println!("1 を足すと 3 の倍数"),
        _ => unreachable!(),
    }
}

unreachable!() には到達しえないものとみなされ,エラーが無くなります.

もし unreachable!() と書いた部分に到達してしまったら,実行時エラーになります.

match 式の返す値

match 式の返す値は,全て同じ型でなければなりません.次のコードはエラーになります.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let y = match x {
        0 => 2i32, // i32
        _ => 2.5f64, // f64
    }; // y の型は?
}
error[E0308]: `match` arms have incompatible types
  --> src/main.rs:9:14
   |
7  |       let y = match x {
   |  _____________-
8  | |         0 => 2i32,
   | |              ---- this is found to be of type `i32`
9  | |         _ => 2.5f64,
   | |              ^^^^^^ expected `i32`, found `f64`
10 | |     };
   | |_____- `match` arms have incompatible types

ただし, => の右が break 式や continue 式, unreachable!() などの場合は値を返す必要がありません.

use proconio::input;

fn main() {
    loop {
        input! {
            x: i32,
        }
        let y = match x {
            0 => break,
            x => x - 1,
        };
        println!("{}", y);
    }
}

マッチガード

パターンと => の間に, if を使って条件を書くことができます.

fn main() {
    let tuple = (1, 3);
    match tuple {
        (1, x) if x % 2 == 0 => println!("{}", x),
        _ => {}
    }
}

タプル (1, 3) はパターン (1, x) にマッチしますが,その後の条件 x % 2 == 0 を満たさないので,何も出力されません.

@ 演算子

次のプログラムは,タプル tuple の最初の要素が 1 のとき,次の要素の桁数を出力します.

fn main() {
    let tuple = (1, 50);
    match tuple {
        (1, 0..=9) => println!("1 桁"),
        (1, 10..=99) => println!(" 2 桁"),
        (1, 100..=std::i32::MAX) => println!("3 桁以上"),
        (1, _) => println!("負"),
        _ => {}
    }
}

ここで, 1 桁2 桁 ではなく 7 は 1 桁50 は 2 桁 のように出力するには,どうすればよいでしょうか.

パターンの直前に,変数名と @ を付けると,そのアームの中で変数名が使えるようになります.

fn main() {
    let tuple = (1, 50);
    match tuple {
        (1, value @ 0..=9) => println!("{} は 1 桁", value),
        (1, value @ 10..=99) => println!("{} は 2 桁", value),
        (1, value @ 100..=std::i32::MAX) => println!("{} は 3 桁以上", value),
        (1, _) => println!("負"),
        _ => {}
    }
}

0..=9 の代わりに value @ 0..=9 と書くと, tuple の 2 つめの要素がパターン 0..=9 にマッチしたとき,その値が value として使えるようになります.

練習問題