Chapter 11

パターンマッチング

mebiusbox
mebiusbox
2023.03.16に更新

📌 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
}

📌 マッチガード

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

let num = Some(4);

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

📌 パターンによる束縛

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

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式も使えます.