あなたのRustすぐにパニックしませんか？ - unwrap of Death -

# はじめに

Rustはクラウド業界から組込み業界まで幅広く使われるようになりました。

こんなにコンパイラに怒られまくるマゾい言語が流行っているなんて、最高です。お前ら。

私はお仕事でRustを使っており、Rust布教をしています。

Rustは安全と言われていますが、すぐにPanicするプログラムに溢れています。サンプルコードをコピペすると簡単にPanicするプログラムが出来上がります。なぜでしょうか？


この記事では**unwrap警察**として、世の中のRustからPanicを減らす方法について書いてみます。


# アンラッパー

Optional型がある言語(SwiftとかJava8とか)から来た人を除いて、Rustのtry/catchではなく、OptionやResultでエラー処理をするスタイルに違和感を覚えた人も多いでしょう。

## Option::unwrap()

まずはRust基本テクニックの`Option::unwrap()`です。これは`Option`が`Some`なら値を返して、`Err`だったらPanicします。

 <INS>**「このunwrap()はオマジナイ」** と聞いたら、unwrap警察の出番です。 </INS>

```rust
    let something:Option<i32> = Some(3);
    println!("something:{}", something.unwrap());

    let something:Option<i32> = None;
    println!("something:{}", something.unwrap()); // <- Panic!
```

この例ではシンプルかもしれませんが、

```rust
something.unwrap().unwrap().unwrap().do_something()
```

こういうchainになったとき、より難解さが向上します。


## Result::unwrap()

次に戦わないといけないのは`Result::unwrap()`です。
Rustのエラーハンドリングは、まだ過渡期で、振り回されている人も多いでしょう。

`Result`は以下のように正常値の型と、エラーの型を指定します。

```rust

let my_result:Result<i32, String> = Ok(123); // or Err("my error")
```


これも`unwrap()`の温床になります。

```rust
let something:Result<i32, String> = Ok(3);
println!("something:{}", something.unwrap());

let something:Result<i32, String> = Err("error desu".to_string());
println!("something:{}", something.unwrap()); // <- Panic
```

こんな感じでPanicします。


Rustではエラー型の定義や補足が難しいですね。


## アンラッパーを捕まえる

気軽に`unwrap()`することで、流れるようにpanicするコードになってしまいます。

プログラマの犯罪に、`tryするけどcatchで無視`がありますが、`unwrap()`も同レベルの犯罪とみなされる日も近いです。

あなたは今から、気をつけて`unwrap()`を使うでしょう。
大きな組織での開発になったとき、どのようにするべきでしょうか？


## `unwrap()`を禁止しよう

やや過激にunwrapを禁止してみましょう。

Clippyでは以下のコマンドで、`unwrap()`をエラーにすることができます。

```shell
$ cargo clippy -- -D clippy::unwrap_used

error: used `unwrap()` on a `Result` value
 --> src/main.rs:8:30
  |
8 |     println!("something:{}", something.unwrap());
  |                              ^^^^^^^^^^^^^^^^^^
```


世の中には、[unwrap()を禁止するためのcargoモジュール](https://github.com/hhatto/cargo-strict)を作っている方もおり、`unwrap()`が人類の敵だとおわかりでしょうか。

## `unwrap()`がない世界での生き方

とは言え、`unwrap()`も目的があって使われていたので、代わりの実装が必要になります。

### `unwrap()` of Death

まず簡単なケースは、本当にPanicで死んでいいケースです。

次の例は環境変数`MY_ENV`が実行に必須であり、ない場合は終了していいケースです。

```rust
let env = std::env::var("MY_ENV").expect("You must set MY_ENV. Abort.");
```

ですが、これだとスタックトレースも出てしまいます。ユーザー層によってはスタックトレースを表示すると（人間が）パニックを起こします。

Rust1.65.0で導入された `let-else`構文を使って以下のように書いてみましょう。

```rust
let Ok(env) = std::env::var("MY_ENV") else {
    eprintln!("You must set MY_ENV. Abort.");
    std::process::exit(1);
};

// 以降、envはStringとして使える
```

これだと丁寧に終了できます。終了コードも設定できます。

`unwrap()`だけでなく、`expect()`と`panic!()`も取り締まりたい場合は、以下のようにClippyを使います。

```shell
$ cargo clippy -- -D clippy::expect_used -D clippy::panic  -D clippy::unwrap_used
```

## `unwrap()`と戦わないといけないケース

`Option`や`Result`を正しく皮むきし、エラー処理が必要なケースです。

Rustでは`match`構文や`if-let`構文、先述した`let-else`構文が使えます。

多くの場合はこれらの構文でカバーしましょう。

```rust
//matchケース
let something:Result<i32, String> = Ok(3);
match something {
    Ok(i) => println!("something:{}", i),
    Err(e) => eprintln!("error:{}", e),
}

//if-letケース
let something:Result<i32, String> = Ok(3);
if let Ok(i) = something {
    println!("something:{}", i);
}
//エラー処理してないよ
```

ただ、`Option`や`Result`が入れ子になっている場合は大変です。

この例のように、入れ子で変数を定義することはなくても、処理の途中で入れ子になるケースがあります。

```rust
let something:Option<Result<Result<i32, String>, String>> = Some(Ok(Ok(3)));

match something{
    Some(o2)=>{
        match o2{
            Ok(o3)=>{
                match o3{
                    Ok(i)=>{
                        println!("something:{}", i);
                    }
                    Err(e)=>{
                        eprintln!("error in 2nd Result:{}", e);
                    }
                }
            }
            Err(e)=>{
                eprintln!("error in 1st Result:{}", e);
            }
        }
    }
    None=>{
        eprintln!("1st Option is None");
    }
}
```

この場合、`match`は入れ子でパターン指定できるので、以下のように短く書けるケースもあります。

```rust
let something:Option<Result<Result<i32, String>, String>> = Some(Ok(Ok(3)));

match something{
    Some(Ok(Ok(i)))=>{
        println!("something:{}", i);
    }
    Some(Ok(Err(e)))=>{
        eprintln!("error in 2nd Result:{}", e);
    }
    Some(Err(e))=>{
        eprintln!("error in 1st Result:{}", e);
    }
    None=>{
        eprintln!("1st Option is None");
    }
}
```




### 復習：アンラッパーのコード

ここで`unwrap()`のコードを復習してみます。

```rust
let something:Option<Result<Result<i32, String>, String>> = Some(Ok(Ok(3)));

println!("something:{}", something.unwrap().unwrap().unwrap());
```

わずか1行で書けていますね。

`Option`や`Result`のchainを`unwrap()`なしでシンプルに書く方法はないでしょうか？Chainしたいですよね。

## ？演算子と、anyhowを使う

？演算子は `Result`の後ろつけます。値が`Ok`なら値を返し、値が`Err`なら`Err`をReturnします。

Returnするので関数の戻り値の型にあわせる必要があります。


Rustは戻り値の型を明示的に指定する必要がありますが、Rust標準ではこの記述が難しいです。

この課題には[anyhow](https://crates.io/crates/anyhow)を使えます。anyhowは様々なErrorの抽象型のように使えるものです。

以下のように、異なる`Result`の型の違いを`anyhow::Result`は吸収できます。

```rust
fn handle_many_error() -> anyhow::Result<i32>{

    let s = std::fs::read_to_string("./test.txt")?; // エラーだとstd::io::Result<String>を返す

    Ok(s.parse()?) // エラーだとstd::num::ParseIntErrorを返す
    
    //以下のようにも書ける
    //s.parse().map_err(anyhow::Error::msg)
}
```

これで`Result`は何とかできそうです。

しかし、Rustでは？演算子は `Option`に使えません。これもanyhowが役立ちます。

```rust
use anyhow::Context as _;

//Contextが衝突しないなら、以下でもいい
use anyhow::Context
```

これを書くと、既存の`Option`に`context()`が生えます。
`context()`は`Option`を`anyhow::Result`に変換できます。

```rust
let hi = Some(3);
hi.context("nya"); //anyhow::ResultのOk(3)になる。hiがNoneだったらErr("nya")になる。

//Optionなので？演算子が使えるようになる
let value = hi.context("nya")?
```



これと？演算子を組み合わせると、`match`が入れ子になっていたケースは以下のように書けます。

```rust
use anyhow::Context as _;
use anyhow::Result;

fn do_something() -> anyhow::Result<i32> {

    let something:Option<Result<Result<i32>>> = Some(Ok(Ok(3)));

    something
        .context("1st Option is None")?
        .context("error in 1st Result:")?
        .context("error in 2nd Result:")
}

fn main() {
    match do_something(){
        Ok(i)=>{
            println!("something:{}", i);
        }
        Err(e)=>{
            eprintln!("error:{:#}", e); // Resultにcontextを使う場合は、{}ではなく{:#}でないと内側のエラーが表示されない
        }
    }
}
```

この例の`something.context()`な処理部は、元々の`Result`の`Err`を返すだけでいいなら以下のよう書けます。

```rust
//??は連続で利用できる
something.context("1st Option is None")??
```

`context()`やanyhowがなくても`Option:ok_or()`で`Option`を`std::result::Result`に変換することはできます。しかし、その場合は？演算子で返す型の指定が難しくなります。

そこで、`anyhow::Result`を使うことで、？演算子が使いやすくなり、結果シンプルな記述ができるようになります。


# 最後に

「Rustは安全」と言われつつも危険な使い方があり、それを避けるコーディング方法を学ぶ必要があります。

unwrapの使い方を考えて、Panicしないプログラムを書いていきましょう。

Rustはいいぞ。もっとやれ。