Rustの「if let」とは何なのか？

# Rust入門者とif letとの出会い
if let文は[公式チュートリアル](https://doc.rust-jp.rs/book-ja/title-page.html)では、[パターンマッチングの章](https://doc.rust-jp.rs/book-ja/ch06-03-if-let.html)で初登場します。入門者は`match`によるパターンマッチングを学んだ後で、「値が一つのパターンにマッチした時の動作だけを書きたいなら、`match`の代わりに`if let`という記法を使えば短く書けるよ」と習うことになります。

```rust
//https://doc.rust-jp.rs/book-ja/ch06-03-if-let.htmlから引用

// 値がSome(3)の時だけコードを実行するmatch:
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`を使えばいいんだな」とストンと腑に落ちる人は、何の問題もありません。次に進みましょう。そうではなく、「**なんだこの構文？パターンマッチの話をしてたのにどうして急に`if`と`let`が......?**」と立ち止まってしまった（私のような）人達[^1]に向けてこの記事を書きました。

# 公式の`if let`サンプルは分かりにくい
正直、`if let`文については公式チュートリアルの説明が分かりにくいと思っています。上の例を見て、「**普通にif文を使えば？**」と思った方は少なくないでしょう。実際、これはif文を使うべき場面です（チュートリアルの説明順序上の都合があるんだと思いますが......）。

じゃあどういう時に`if let`を使うのか？たとえば、下のような時です。

```rust
// ValueがNoneでなければprintする関数
fn print_if_exist(value: Option<u8>) {
    if let Some(x) = value {
        println!("Value {} exists!", x);
    }
}
```

さっきの例と違って、Some(`x`)のように変数が左辺に含まれていて、その`x`を{}内で利用していることに注目してください。言語化すれば「**パターンマッチングと変数束縛を同時に行いたい時**」ということになります。今はまだこの言葉の意味が分からなくても大丈夫です。以下順を追ってこの構文が一体何なのかを説明します。

# `if let`は既存の`if`と`let`を利用したテクニックではない
まず最初にはっきりさせておくべきは、`if let`記法が「既存の`if`と`let`を利用した頭の良いテクニック」ではなく、`if let`という新たなキーワードを使った、れっきとした別の記法であるということです。実際、導入時には、`if_let`や`iflet`といった書き方も[検討された形跡](https://github.com/rust-lang/rfcs/pull/160)があります。`else if`や`extern crate`もそうですが、Rustは既存のキーワードの組み合わせで新たなキーワードを作ることに抵抗がないようですね。

とはいえ、わざわざ`if`と`let`という既存のキーワードを組み合わせて作る訳ですから、この記法には`if`文の気持ちと`let`文の気持ちが入っていることになります。ではどういう風に`if let`記法を読めば腑に落ちるのかを次に見ていきます。

# if let記法の構造
改めてif let記法の構造を書き下しましょう。
一般的な形は以下のようになります。実は`else`節も使えます。
<img width="80%" alt="if letの構造" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/160540/84421632-46c9-7af1-10ab-6acb3be604e7.png">
ここで、**いったん色々飲みこんで`let hoge = piyo`の部分がtrue/falseを返すboolであると仮定**してみることにしましょう（**この仮定自体は誤り**ですが、文の「お気持ち」を掴むには有効なので）。すると、文構造自体は普通の`if`文そのものと気付くと思います。このように考えると、上の文の意味するところは

*「`let hoge = piyo` という操作の真偽をifで判定して、{}内の動作を切り替える」*

と捉えてよさそうです。とすると次の問題は`let hoge = piyo`は何をしているのか？です。

# letの真の意味
`let hoge = piyo`が「判定」に使われるというのは、奇妙に感じられるかもしれません。というのも、多くの入門者は「let hoge = piyo」の意味するところを「**値piyoを変数hogeに代入する**」と習ったはずだからです（[公式チュートリアル](https://doc.rust-jp.rs/book-ja/ch03-01-variables-and-mutability.html)もそう説明している）。しかし実は、公式チュートリアルの後半、[18章](https://doc.rust-jp.rs/book-ja/ch18-01-all-the-places-for-patterns.html)で種明かしされているように、letの「真の姿」は以下のような形をしているのです。

```
let PATTERN = EXPRESSION;
// EXPRESSIONとは、何らかの値を返す「式」のこと
// PATTERNとは、値がマッチするか否かに用いられる「パターン」のこと
```

左辺が「変数」のようなものではなく、「パターン（PATTERN）」であることに着目してください。letの真の動作とは「**右辺の式（EXPRESSION）が返す値を左辺のパターン（PATTERN）に当てはめ、PATTERNに従って変数に値を束縛する**」というものだったのです。たとえば、`let x = 5;`のような単純な代入が実際に行っている内容を大仰に書き下すと、「**右辺の式が返す値"5"を左辺のパターン"x"に当てはめて、"x"に"5"を束縛する**」という動作をしていることになります。

このような動作の恩恵は、より柔軟な代入が可能となる点です。下の例を見てください。右辺のタプルを分配して左辺に代入する、といった芸当が可能になっていることが分かります。

```rust
let (x, y) = (3, 4);
assert_eq!(x, 3); // xに3が代入されている
```

もし右辺の値が左辺のパターンに当てはまらなかったら？
以下のコードがその例です。左辺のパターンは2要素のタプル（x, y）ですが、右辺の式として3要素のタプルを与えました。これは明らかにマッチせず、「mismatched types」であるとコンパイル時にエラーで怒られることになります。

```rust
let (x, y) = (3, 4, 5); // コンパイルエラー: mismatched types
```

では、次の例はどうでしょうか。
左辺のパターンとして`Some(x)`、右辺の式として`Option<u8>`型の変数`value`を与えました。

```rust,no_run
// 受け取ったvalueの中身をprintしたかった関数
fn print_value(value: Option<u8>) {
    let Some(x) = value; // コンパイルエラー: refutable pattern
    println!("value is {}.", x);
}
```
たとえば`value`が`Some(3)`の時に「x = 3」のようにマッチしても良い、と思うかもしれません。が、実際にはコンパイルエラーになります。

エラーメッセージは「**refutable pattern in local binding: `None` not covered**」。直訳すれば「**変数の束縛に論駁可能なパターンが使われています。`None`の場合がカバーされていません**」。つまり、右辺は`Some(x)`または`None`の２つのパターンが考えられるのに対し、左辺は`Some(x)`のパターンしか考慮されていないので、 パターンに合致しない可能性がある（= 論駁可能)ということです[^2]。**`let`文は、論駁不可能なパターンしか受け付けません**。この制限によって、`let`文は意味的には結局「代入」を表す文として動作することになります。

# if letの登場: 論駁可能なパターンへの対応
前項の話をまとめると、以下のようになります。
- 本来`let`にはパターン判定の意味がある
- パターン判定には「論駁可能性」が存在し、「論駁可能」な場合はプログラム実行中にパターンが一致したりしなかったりする
- 実際の`let`文は、論駁不可能なパターンしかコンパイルを許さないので、実行中に「パターン不一致」の判定を下す機会はない

こう並べられてみると、「**`let`キーワードを使って、実行中のパターン不一致に対応した構文があっても良いのではないか？**」と思うのは自然な発想のように思います。それが`if let`文です。
以下のコードはコンパイル可能です。

```rust
// ValueがNoneでなければprintする関数
fn print_if_exist(value: Option<u8>) {
    if let Some(x) = value {
        println!("Value {} exists!", x);
    }
}
```

「**もし`let`によるパターンマッチングが成功するのなら、xに値を束縛して{ }スコープ内の命令を実行する**」

今なら自然に読めてきませんか？

# 2022/11/10追記: let-else文
Rust ver. 1.65.0にて、長らく待望の「let-else文」が安定化されました。
https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html
これにより「実行中のパターン不一致に対応した`let`系構文」がもう一つ増えたことになります。

論駁可能なパターンに対して、マッチする場合のみ変数を束縛する、という動作は両者に共通しています。違うのは、`if let`が「新たに作ったブロック」内で変数を束縛するのに対し、`let else`は「現在のブロック」で束縛する、という点です。

```rust
// 変数のxへの束縛範囲

// if-let文
if let Some(x) = Some(3) { 
    // ------↓ここから束縛↓------
    assert_eq!(x, 3);
    // ------↑ここまで↑------
} else {}

// let-else文
let Some(x) = Some(4) else {
    return
};
// ------↓ここから束縛↓------
assert_eq!(x, 4);

```

束縛した変数を長く使いたいなら`let-else`が便利です。


# 参考サイト
- 論駁可能性について: https://doc.rust-jp.rs/book-ja/ch18-01-all-the-places-for-patterns.html
- `if let`導入に関する議論: https://github.com/rust-lang/rfcs/pull/160

[^1]: e.g.) [a](https://users.rust-lang.org/t/i-have-a-hard-time-understanding-the-if-let-syntax/48816), [b](https://stackoverflow.com/questions/72081657/rust-lang-what-is-if-let-somex-x-doing), [c](https://www.reddit.com/r/rust/comments/gnx6yq/if_let_but_why/)

[^2]: ここでは分かりやすさ重視のためにvalueを変数としましたが、単に`let Some(x) = Some(3)`のような構文でもコンパイルエラーになります。この場合はマッチしない可能性が無い（Noneの可能性が無い）訳ですが、あくまでも「型理論上は」論駁可能であるためにこのような挙動になります。