Chapter 11

パターンマッチング

📌 match 式

パターンマッチング は,式の値がパターンに一致するかしないかを判定する仕組みです. match 式は,パターンマッチングによって評価する式を変えるときに使います.

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

match 式はパターンと式を => で結合したものを並べたものです.この パターン => 式 のことを アーム (arm)と言います. match 式はアームのパターンを順番に処理していき,最初にパターンに一致した式を評価してその結果を返します.そして,パターンに一致したアーム以降は処理されません.このような仕組みを短絡評価またはショートサーキットと呼びます

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

match 式はマッチング対象のオブジェクトが取りうる値をすべて網羅しなければなりません.そのため,記述したアーム以外に一致する ワイルドカード_)を使うことができます.

let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

Option 型が束縛しているオブジェクトを match 式で取り出すことが出来ます.ここで注意なのが, match 式でパターンが一致したときに,束縛しているオブジェクトを受け取りますが,そのとき所有権も移動しているということです.

fn main() {
    let a: Option<String> = Some(String::from("hello"));
    match a {
        Some(x) => println!("{}", x), // move ownership
        None => ()
    }
    println!("{:?}", a);
    //               ^ value borrowed here after partial move
    // error[E0382]: borrow of partially moved value: `a`
}

これは何が起きているかと言うと,変数 a が束縛している Option 型のオブジェクトが,内部で束縛しているオブジェクトの所有権をアームのパターンによって取り出され所有権が渡されています.これにより,変数 a は束縛したままですが,その内部では何も束縛していないことになります.なので,部分移動(partial move)が発生していることになりエラーとなります.この部分移動に対応する方法として次の2つがあります.

1つはアームのパターンでオブジェクトを参照で受け取る方法です.注意なのが,パターンで参照を取得するときは ref を使います.可変参照なら ref mut です.

fn main() {
    let a: Option<String> = Some(String::from("hello"));
    match a {
        Some(ref x) => println!("{}", x), // reference
        None => ()
    }
    println!("{:?}", a); // borrow check!! - OK
}

もう1つは,返り値としてオブジェクトを返すことです.

fn main() {
    let a: Option<String> = Some(String::from("hello"));
    let a = match a {
        Some(x) => { println!("{}", x); Some(x) }
        None => None,
    };
    println!("{:?}", a); // borrow check!! - OK
}

📌 パターンによる変数束縛

変数に束縛するときにパターンを使って分解出来ることを覚えていますか?
この分解束縛のパターンでも参照を使うことが出来ます.

struct Account { name: String, pass: String }

fn main() {
    let a = Account { name: String::from("name"), pass: String::from("pass") };
    let Account { name, pass } = a;    // move ownership
    println!("{} {}", name, pass);     // borrow check!! - OK
    println!("{} {}", a.name, a.pass); // borrow check!! - Error
}

上記のコードはパターンによる分解束縛時に所有権も移動してしまい,借用チェックでコンパイルエラーになります.その場合は,パターンに ref を使って不変参照で受け取ることで借用チェックに通ることになります.

struct Account { name: String, pass: String }

fn main() {
    let a = Account { name: String::from("name"), pass: String::from("pass") };
    let Account { ref name, ref pass } = a; // reference
    println!("{} {}", name, pass);     // borrow check!! - OK
    println!("{} {}", a.name, a.pass); // borrow check!! - OK
}

📌 if let 式

match 式のアーム(ワイルドカード以外)が1つのときは if let 式を使うと短く記述することが出来ます.例えば次のコードを見てください.

let some_u8_value = Some(0u8);
match some_u8_value {
    Some(3) => println!("three"),
    _ => (),
}

if let 式を使うと次のように短く記述することが出来ます.

if let Some(3) = some_u8_value {
    println!("three");
}

if let と同様に while let も使うことが出来ます.

📌 マッチガード

マッチガードmatch 式のアームのパターンに,さらに if 条件を加えることができるものです.これにより,より複雑なパターンを扱えます:

let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}