if let

列挙型をマッチさせるとき、場合によってはmatchを使用すると不自然な書き方になってしまう場合があります。例えば...

#![allow(unused)]
fn main() {
// Make `optional` of type `Option<i32>`
// `optional`という変数の型を`Option<i32>`に指定
let optional = Some(7);

match optional {
    Some(i) => {
        println!("This is a really long string and `{:?}`", i);
        // ^ Needed 2 indentations just so we could destructure
        // `i` from the option.
        // ^ `i`をoption型からデストラクトするためだけに
        // インデントが一つ増えてしまっている。
    },
    _ => {},
    // ^ Required because `match` is exhaustive. Doesn't it seem
    // like wasted space?
    // ^ `match`は全ての型に対して網羅的でなくてはならないので必要。
    // 冗長に見えませんか?
};

}

この場合はif letを用いたほうが美しく、失敗時の処理も柔軟に行うことができます。

fn main() {
    // All have type `Option<i32>`
    // 全て`Option<i32>`型
    let number = Some(7);
    let letter: Option<i32> = None;
    let emoticon: Option<i32> = None;

    // The `if let` construct reads: "if `let` destructures `number` into
    // `Some(i)`, evaluate the block (`{}`).
    // `if let`文は以下と同じ意味.
    //
    // もしletがnumberをデストラクトした結果が`Some(i)`になるならば
    // ブロック内(`{}`)を実行する。
    if let Some(i) = number {
        println!("Matched {:?}!", i);
    }

    // If you need to specify a failure, use an else:
    // デストラクトした結果が`Some()`にならない場合の処理を明示したい場合、
    // `else`を使用する。
    if let Some(i) = letter {
        println!("Matched {:?}!", i);
    } else {
        // Destructure failed. Change to the failure case.
        // デストラクト失敗の場合。このブロック内を実行
        println!("Didn't match a number. Let's go with a letter!");
    }

    // Provide an altered failing condition.
    // デストラクト失敗時の処理を更に分岐させることもできる
    let i_like_letters = false;

    if let Some(i) = emoticon {
        println!("Matched {:?}!", i);
    // Destructure failed. Evaluate an `else if` condition to see if the
    // alternate failure branch should be taken:
    // デストラクト失敗。`else if`を評価し、処理をさらに分岐させる。
    } else if i_like_letters {
        println!("Didn't match a number. Let's go with a letter!");
    } else {
        // The condition evaluated false. This branch is the default:
        // 今回は`else if`の評価がfalseなので、このブロック内がデフォルト
        println!("I don't like letters. Let's go with an emoticon :)!");
    }
}

同じように、if letを列挙型の値にマッチさせるのに利用できます。

// Our example enum
// 列挙型の例
enum Foo {
    Bar,
    Baz,
    Qux(u32)
}

fn main() {
    // Create example variables
    // 変数の例を作成する
    let a = Foo::Bar;
    let b = Foo::Baz;
    let c = Foo::Qux(100);
    
    // Variable a matches Foo::Bar
    // 変数aはFoo::Barにマッチする
    if let Foo::Bar = a {
        println!("a is foobar");
    }
    
    // Variable b does not match Foo::Bar
    // So this will print nothing
    // 変数bはFoo::Barにマッチしないので、これは何も出力しない
    if let Foo::Bar = b {
        println!("b is foobar");
    }
    
    // Variable c matches Foo::Qux which has a value
    // Similar to Some() in the previous example
    // 変数cはFoo::Quxにマッチし、値を持つ
    // 以前のSome()の例と同様
    if let Foo::Qux(value) = c {
        println!("c is {}", value);
    }

    // Binding also works with `if let`
    // `if let`でも束縛は動作する
    if let Foo::Qux(value @ 100) = c {
        println!("c is one hundred");
    }
}

if letを利用する別の利点は、パラメータ化されていない列挙型の値をマッチさせられることです。 これは、列挙型がPartialEqを実装もderiveもしていない場合でも同様です。 PartialEqがない場合には、if Foo::Bar == aはコンパイルできません。 列挙型のインスタンスは比較できませんが、if letを使えば動作します。

次の例をif letを利用して修正するのにチャレンジしてみましょう。

// This enum purposely neither implements nor derives PartialEq.
// That is why comparing Foo::Bar == a fails below.
// この列挙型はわざとPartialEqを実装もderiveもしていない
// ゆえに以下でFoo::Bar == aの比較が失敗する
enum Foo {Bar}

fn main() {
    let a = Foo::Bar;

    // Variable a matches Foo::Bar
    // 変数aはFoo::Barにマッチする
    if Foo::Bar == a {
    // ^-- this causes a compile-time error. Use `if let` instead.
    // ^-- これはコンパイル時エラー。代わりに`if let`を使う。
        println!("a is foobar");
    }
}

参照

列挙型, オプション, RFC