Rustで「あ、やっぱいいです」したい

あまりいい例が思いつかないのですが、「あわよくば」実行したい処理というのがプログラミングにはよく出てきたりします。

特に今回はRustの `Option` 型の話をします。以下「辞書に載っているかを見て、 `Some` なら情報を出力、 `None` なら何もしない」という関数を考えてみます。なかったら「あ、やっぱり何もしなくていいです」ということで処理を打ち切り、関数を抜けたいわけです。

```Rust
use std::collections::HashMap;

struct Person {
    name: String,
    age: usize,
}

fn search(book: &HashMap<usize, Person>, id: usize) {
    
    // 辞書からidに登録されたPersonを取ってきて、あったら以下を実行

    println!("[HIT] name: {}, age: {}", p.name, p.age);
}

fn main() {
    let mut book = HashMap::new();

    let p = Person {
        name: "namnium".to_string(),
        age: 21,
    };
    book.insert(100, p);

    search(&book, 100); // 出力あり
    search(&book, 200); // 出力なし
}
```

# `unwrap` を使う

他の言語とかだと「バリデーション」というシーンで同様のことを行うことが多いです。

`NULL` チェックと同じノリで、こうやって書きたくなります。

```Rust
fn search(book: &HashMap<usize, Person>, id: usize) {
    let p_wrapped = book.get(&id);
    if p_wrapped.is_none() { return; }
    let p = p_wrapped.unwrap();

    println!("[HIT] name: {}, age: {}", p.name, p.age);
}
```

`is_none` メソッドでバリデーションし、ダメなら抜ける、大丈夫なら `unwrap` という感じです。

「ダメだったらリターン」はどの言語でも「メインの処理が条件分岐の枝には入ってこない」ので、インデントを深くしたくない場合おすすめの書き方です。

しかし `unwrap` を使うのは正直あまり美しくないです...

## 2021/4/24 追記

この方向性で行く場合、`anyhow`クレートの`ensure!`マクロを使い、さらに冗長にするといい感じかもしれません。一つの選択肢として追加しておきます。(シグネチャは大きく変わっています。)

ただ表題は解決していない感じなのと、やっぱり今回は`unwrap`を使うことになるのがなんとも言えないです。このシーンで`Result`型を返す場合は、後述の`?`のほうが賢いです。

```rust
fn search(book: &HashMap<usize, Person>, id: usize) -> Result<String> {
    let p_wrapped = book.get(&id);
    ensure!(p_wrapped.is_some(), "book doesn't have id:{} item.", id);
    let p = p_wrapped.unwrap();

    let res = format!("[HIT] name: {}, age: {}", p.name, p.age);

    Ok(res)
}
```

# `if let` を使う

Rustらしい書き方として `if let` 式の使用が考えられます。

```Rust
fn search(book: &HashMap<usize, Person>, id: usize) {
    if let Some(p) = book.get(&id) {
        println!("[HIT] name: {}, age: {}", p.name, p.age);
    }
}
```

パターンマッチングによるアンラップはとてもRustらしいでしょう。

しかし、メインの処理にも関わらずインデントが深くなってしまうのがいただけないです。今回みたいな例ならいいですが、メインの処理が10行以上になってくるとこの方法はまずいです。

# `match` を使う

Rustらしくパターンマッチを活かしつつ、インデントも保ちたいとなると、 `match` 式か `if let ... else` 式を使用し `unwrap` の代用とする案が考えられます。今回は `match` を使用します。

```Rust
fn search(book: &HashMap<usize, Person>, id: usize) {
    let p = match book.get(&id) {
        Some(v) => v,
        None => return,
    };

    println!("[HIT] name: {}, age: {}", p.name, p.age);
}
```

しかし `None => return` というわかりきった処理のためにこれだけ行を消費するのはなんとも言えない感覚になります。

# `?` を使う

ここまで記事を読んで「try!マクロをご存知ない？」みたいに思った方もいるかもしれません。そうです、Rustのエラーハンドリング手法を使用することでも解決できます。

```Rust
fn search(book: &HashMap<usize, Person>, id: usize) -> Option<()> {
    let p = book.get(&id)?;

    println!("[HIT] name: {}, age: {}", p.name, p.age);

    Some(())
}
```

しかし関数のシグネチャが変わっていますし、そもそも今回は「エラーが起きるかもしれない関数」の話ではなく「あわよくば実行して欲しい処理をもった関数」の話をしているわけですから、一見きれいに見えますがあまり取りたい手段ではないです。

## 2021/4/23 追記

`?`を使うならばいっそのこと`Result`型を返したほうがよさそうな気もします。(`ensure!`マクロの場合についても考慮すると。)

表題は解決していないですし、シグネチャも全く変わっていますが、返り値のエラー処理という形で書きたい人にとってはこれがベストでしょう。

```rust
fn search(book: &HashMap<usize, Person>, id: usize) -> Result<String, String> {
    let p = book.get(&id)
        .ok_or_else(|| format!("book doesn't have id:{} item.", id))?;

    let res = format!("[HIT] name: {}, age: {}", p.name, p.age);

    Ok(res)
}
```

# やっぱり `match` を使うんだけどマクロ用意してみる

`match` 式を使う方法は、冗長ながらも紹介した中ではバランスの取れた方法だったように思います。これをマクロ化してしまえばすっきりしそうです。

```Rust
macro_rules! or_return {
    ( $wrapper:expr, $failed:expr ) => {
        match $wrapper {
            Some(v) => v,
            None => return $failed,
        }
    };
}

fn search(book: &HashMap<usize, Person>, id: usize) {
    let p = or_return!(book.get(&id), ());

    println!("[HIT] name: {}, age: {}", p.name, p.age);
}
```

具体例が思いつかないとはいいつつもしょっちゅう書く処理なのでこのマクロの導入は本気で良さそうに思えてしまいます。

---

この他に `Option` で使えそうなメソッドに `unwrap_or_else` というのがあるのですが、渡すのはRubyでいうブロックみたいなものではなく思いっきりクロージャなので `return` が実行できないです。

で、ここまで偉そうに解説してきたので一見解説記事に見えますが、「これ以上に良い書き方実はあったりしますか？」という本来teratailとかでやるべき「質問」が本記事の真の目的でした。もしこの他に良い書き方をご存知の方おりましたら、ぜひともコメントお願いいたします()

# 2022/11/04 追記　`let-else`文

Rust 1.65.0にて`let-else`文が追加されました！

本記事で最も求めていたのはまさしくこの構文です。

```rust:Rust
fn search(book: &HashMap<usize, Person>, id: usize) {
    let Some(p) = book.get(&id) else { return };
    println!("[HIT] name: {}, age: {}", p.name, p.age);
}
```

...ただ、現在は「素直に`Result`を返すべきでは？」と考えるようになったので少し複雑な気持ちです。

詳しくは記事を書いたのでこちらを見ていただけると幸いです。

https://qiita.com/namn1125/items/ccedf9cc06b8cef71557