RustのResult<T, E>のEって何にする？

この記事は [Rust Advent Calendar 2021](https://qiita.com/advent-calendar/2021/rust) の 7日目の記事です。

# 結論
よく分からなければ、`anyhow::Error`にしておくのがよさそう。

:::note
2021/12現在 Rustのエラーハンドリングのベストプラクティスというのは定まっていないようである。
なので、Rustのエラーハンドリングがこれからどうなっていくのかは注視する必要がある。
:::

# Rustのエラーハンドリングのキホン
Rustを勉強していくうちに、エラーハンドリングはどうやら`Result<T, E>`というものを使うようだということが分かる。`Result<T, E>`を返す関数内では`?`オペレーターが使え

下記のコードが

```rust
use std::fs::OpenOptions;
use std::io;
use std::io::prelude::*;
use std::path::Path;

fn write(filename: impl AsRef<Path>) -> Result<(), io::Error> {
    let mut file = match OpenOptions::new().write(true).open(filename) {
        Ok(file) => file,
        Err(e) => return Err(From::from(e)), // このコードでFrom::fromは不要ですが、?の動作のdesugarを表現するために書いています。
    };
    match file.write_all(b"Hello, world!") {
        Ok(ok) => ok,
        Err(e) => return Err(From::from(e)),
    };
    Ok(())
}
```

`?`オペレーターを使って、以下のように書き換えることができる。

```rust
use std::fs::OpenOptions;
use std::io;
use std::io::prelude::*;
use std::path::Path;

fn write(filename: impl AsRef<Path>) -> Result<(), io::Error> {
    let mut file = OpenOptions::new().write(true).open(filename)?;
    file.write_all(b"Hello, world!")?;
    Ok(())
}
```

かなりシンプルにエラー伝搬が行えていることが分かるだろう。これはRustの嬉しい点の１つだ。
さらに値がエラーの場合、もし発生したエラーと戻り値のエラーが異なっていても、発生したエラーが戻り値のエラーに対して`From`トレイトを実装していれば、エラーを戻り値のエラー(この場合は`io::Error`)に変換してくれる。

すばらしい。。「例外なんていらんかったんや😊」と思うわけだけが、以下のような疑問が浮かんでくる。

- 階層を持つエラーはどうやってコンテキストを保つのか
- ある関数で複数の種類のエラーが発生する場合、Eはなににしたらよいのか

まず最初の疑問から見ていく。この疑問に答えるためには、まず`Error`トレイトについて説明する必要がある。

# コンテキストの保持

## Errorトレイト
Rustには`Error`というトレイトがある。以下のようなものだ。

```rust
pub trait Error: Debug + Display {
    fn source(&self) => Option<&(dyn Error + 'static)> {
        None
    }
}
```

私は最初勘違いしていたのだが、`Result<T, E>`は別に`E`が`Error`トレイトを実装することを要求してない。別に`String`でも`()`でもなんでもいいのである。
ではこの`Error`トレイトはなんのために存在しているのだろうか。

### Errorトレイトの役割

`Error`トレイトには3つの役割がある。
1. エラーのマーカー
2. 表示方法の提供
3. コンテクストの表現

#### エラーのマーカー
`Error`トレイトを実装している型は、エラーを表現するものであることを表現できるということだ。

#### 表示方法
`Error`トレイトは`Debug`と`Display`の実装を要求している。従って`Error`トレイトを実装する構造体は例えば`println!("{}", e);`や`println!("{:?}", e);`とすればエラーを表示できる。

#### コンテクストの表現
`Error`トレイトは`source`メソッドを持っている。エラーがそのソースを持っていた場合`sorce`トレイトをオーバーライドする。`Error`トレイトの[ドキュメント](https://doc.rust-lang.org/std/error/trait.Error.html)から使用例を引用する。

```rust
use std::error::Error;
use std::fmt;

#[derive(Debug)]
struct SuperError {
    side: SuperErrorSideKick,
}

impl fmt::Display for SuperError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SuperError is here!")
    }
}

impl Error for SuperError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.side)
    }
}

#[derive(Debug)]
struct SuperErrorSideKick;

impl fmt::Display for SuperErrorSideKick {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SuperErrorSideKick is here!")
    }
}

impl Error for SuperErrorSideKick {}

fn get_super_error() -> Result<(), SuperError> {
    Err(SuperError { side: SuperErrorSideKick })
}

fn main() {
    match get_super_error() {
        Err(e) => {
            println!("Error: {}", e);
            println!("Caused by: {}", e.source().unwrap());
        }
        _ => println!("No error"),
    }
}
```

```bash:実行結果
Error: SuperError is here!
Caused by: SuperErrorSideKick is here!
```

`e.source()`を呼ぶことで`SuperError`の原因である`SuperSideError`を取得できていることが分かるだろう。

また、以下のように再帰的に`source`を呼べばエラーのコンテクストをプリントできる。

```rust
   // 【2021/12/29】let e -> let mut eに修正　@nobkzさんありがとうございます。
   let mut e = e.source();
   while let Some(cause) = e {
       println!("Caused by: {}", cause);
       e = cause.source();
   }
```


ただし、これはあまりにもミスするのが簡単なため、Rust公式としてもっと簡単にコンテクストをプリントできるようにしようとする[動き](https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html)があるようである。
また、[eyre](https://github.com/yaahc/eyre)というクレートを使うと簡単にコンテクストを出力できるようである。


# 関数内で複数の種類のエラーが発生しうる場合
次に、2つめの疑問である関数で複数の種類のエラーが発生する場合どうしたらいいのかについて見ていく。
これには2種類の手法がある。`Enum`を使う方法と`Box`を使う方法だ。

## Enum
１つ目の手法は`Enum`を使う方法である。例として`sqlx::Error`の抜粋を示す。

```rust
pub enum Error {
    /// Error occurred while parsing a connection string.
    Configuration(BoxDynError),

    /// Error returned from the database.
    Database(Box<dyn DatabaseError>),

    /// Error communicating with the database backend.
    Io(io::Error),

    Tls(BoxDynError),

    ///.....省略
}
```
https://github.com/launchbadge/sqlx/blob/master/sqlx-core/src/error.rs から改変・抜粋して引用

このようにエラーを定義して、Enumの構成要素それぞれに対して`From`トレイトを実装すれば、`?`オペレーターを使って、複数のエラーを扱うことができる。

```rust:Fromトレイトの実装例
impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Self::Io(e)
    }
}
```

さらにこの`Error`をエラーにするためには`Error`トレイトの実装をしなければならない。
`Debug`は`derive`マクロで自動導出できるが、`Display`は手で実装しなければならない。

またエラーのソースを持っているので`source`関数もオーバーライドしたほうがいいだろう。
まとめると以下のような作業が必要である。

1. 各構成要素に`From`トレイトの実装
2. `Error`トレイトの実装(`source`関数の実装と`Display`トレイトの実装と`Debug`の導出)

これは結構面倒な作業なので、マクロでこのようなボイラープレートを生成してくれる[thiserror](https://github.com/dtolnay/thiserror)というクレートが存在する。


メリデメは以下のようになるだろう。
### メリット
- パターンマッチングでその構成要素を取得できること。

### デメリット 
- 実装が面倒なこと。
- 新しいエラーを返したい場合に、`Enum`を拡張しなければならないこと。

## Box
もう一つの方法は`Box`を使うことだ。`Result<T, E>`のEを`Box<dyn Error>`にしておけば、簡単に複数の種類のエラーに対処することができる。なぜなら`Box`には以下のような実装がされているからである。

```rust 
impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a>
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>
```

つまり、`Error`トレイトを実装した型はそれを`Box`化した型に変換できるということである。

また、`Send`、`Sync`、`'static`境界は必須ではないが、可能ならつけておいた方がいい。
なぜなら、`Send`と`Sync`をつけないとマルチスレッドプログラミングで用いることが難しくなるし、`'static`をつけていないと[`downcast_ref`](https://doc.rust-lang.org/std/error/trait.Error.html#method.downcast_ref)で`Box`化されている具体的な値を得ることができなくなるからである。

`Box<dyn Error + Send + Sync + 'static>`の強化版?として[anyhow](https://github.com/dtolnay/anyhow)というクレートがある。
また、`anyhow`のフォークで、カスタマイズされたエラー報告機能がついた[eyre](https://github.com/yaahc/eyre)というクレートもあるようである。


メリデメは以下のようになるだろう。
### メリット 
- 実装が楽なこと。

### デメリット
- 詳細な値の取得が難しいこと。
    - `downcast_ref`という関数で詳細の値を得ることは可能だが、この関数を多用するようなら、`Enum`でエラーを構成したほうが良さそうだ。


# まとめ Result\<T, E>のEは何にするのがよいか
エラーの値が単一なら好きにしたらいいだろう。ただし、`Box`化されたエラーに変換できるように`Error`トレイトは実装した方がよい。

複数の種類のエラーが返される場合は`Enum`(`thiserror`)を使うか`Box`(`anyhow`, `eyre`)を使うかの選択肢がある。
エラーの詳細の値を扱いたい場合は`Enum`、ただエラーを報告したいだけのときは`Box`を使うというのが判断基準だろうか。
アプリケーションのコードでは、ただエラーを報告したいということが多いので`Box`(`anyhow`, `eyre`)を使うのが安定なことが多く、逆にライブラリの場合は、なるべく柔軟性を維持するため`Enum`(`thiserror`)使うことが多いようである。

結論として、ライブラリを書く人よりアプリケーションを書く人の方が多いことと、ライブラリを書く人はRustについて詳しい人が多いということを考えると、悩んだら`anyhow`を使うのが安定の場合が多いということになるだろう。

# 参考文献
- What the Error Handling Project Group is Working Towards https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html
- RustConf 2020 - Error handling Isn't All About Errors by Jane Lusby https://www.youtube.com/watch?v=rAF8mLI0naQ
- Jon Gjengset, Rust for Rustaceans, no strech press https://nostarch.com/rust-rustaceans
- Error Handling In Rust - A Deep Dive https://www.lpalmieri.com/posts/error-handling-rust/ 
- Rust エラー処理2020 https://cha-shu00.hatenablog.com/entry/2020/12/08/060000