Rustのエラー処理

この記事は[Wanoアドベントカレンダー](https://qiita.com/advent-calendar/2018/wano-group)の5日目の記事です。

## 2020/07現在の状況について

エラー処理周りは状況が少し変わりました。
具体的には標準ライブラリ(std)の`std::error::Error`にbacktraceに関する機能追加が行われ、`failure::Fail`が不要になりました。
引き続きfailureを使っても良いのですが、最近は`failure::Error`相当の物を使う為に[anyhow](https://crates.io/crates/anyhow)を、`std::error::Error`を簡単にimplする為に[thiserror](https://crates.io/crates/thiserror)を使うのがおすすめです。

## TL;DR

* Result型の値の項の後ろに`?`を付けると例外っぽく振舞う
* [failure](https://crates.io/crates/failure)を使う

## エラー処理の方法

Rustには例外がありません。
Go言語と同じように戻り値でエラーを返します（同様にpanicもあります）。

Rustでは『処理結果もしくはエラー内容』を表す型として[Result型](https://doc.rust-lang.org/std/result/enum.Result.html)というのがあります。
Result型はenumとして次の様に定義されています。

~~~~rust
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
~~~~

`Ok(T)`が正常に処理できた結果、`Err(E)`が何らかのエラーが発生したことを表します。

例えば、バイト列からStringを作る[String::from_utf8](https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8)メソッドは戻り値の型が`Result<String, FromUtf8Error>`です。
これはStringを作る処理が成功したら`Ok(String)`が返り、不正なutf8バイト列を与える等すると`Err(FromUtf8Error)`が返る事を表しています。

呼び出し側はこの`Result`を`match`によるパターンマッチか、`Result`型のメソッドの`map`等を繋げるメソッドチェーンで処理します。

パターンマッチで処理する場合:

~~~~rust
use std::fs;

fn main() {
    match fs::read_to_string("/file_does_not_exist") {
        Ok(s) => println!("Content => {}", s),
        Err(err) => eprintln!("IO Error => {}", err),
    }
}
~~~~

メソッドチェーンで処理する場合:

~~~~rust
use std::fs;

fn main() {
    fs::read_to_string("/file_does_not_exist")
        .map(|s| println!("Content => {}", s))
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}
~~~~

どちらを使うかは書いている処理に合わせて選択します。

## `?`オペレーター

エラー処理をする時に全てのエラーをその場で処理する事はそんなになく、呼び出し元にエラー処理を委ねる事が多いです。

これを普通に書くと下記の様にいちいちmatchしてreturnしなくてはならず、とても面倒です。

~~~~rust
use std::fs;
use std::io;

fn load_2files_and_print() -> Result<(), io::Error> {
    match fs::read_to_string("/file_does_not_exist_1") {
        Ok(s) => println!("Content1 => {}", s),
        Err(err) => return Err(err),
    }

    match fs::read_to_string("/file_does_not_exist_2") {
        Ok(s) => println!("Content2 => {}", s),
        Err(err) => return Err(err),
    }

    Ok(())
}

fn main() {
    load_2files_and_print()
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}
~~~~

そこでこの手間を減らす為に`?`オペレーターという機能があります。
これは`Result`を返す式の最後に`?`を書くと、`Ok(T)`ならば`T`の値を返し、`Err(E)`ならば`Err(E)`をreturnしてくれるという機能です。
また単に`Err(E)`をreturnするだけではなく、関数の戻り値型が`Result<T, E2>`の時に`From::from`による型変換も自動的に行ってくれます。

これを使うと上記のコードは次の様になります。

~~~~rust
use std::fs;
use std::io;

fn load_2files_and_print() -> Result<(), io::Error> {
    println!("Content1 => {}", fs::read_to_string("/file_does_not_exist_1")?);
    println!("Content2 => {}", fs::read_to_string("/file_does_not_exist_2")?);
    Ok(())
}

fn main() {
    load_2files_and_print()
        .unwrap_or_else(|err| eprintln!("IO Error => {}", err));
}
~~~~

圧倒的にシンプルになりました。

## 独自のエラー型を返す

※ 2020/07/24追記: 現在では[thiserror](https://crates.io/crates/thiserror)を使って`std::error::Error`を実装するのが簡単でおすすめです。

ライブラリ等を書いていると独自のエラー型を返したくなる事があります。
そのような時は[failure crate](https://crates.io/crates/failure)を使ってエラー型を定義すると簡単に独自エラー型を用意できます。

failureは[Fail trait](https://docs.rs/failure/0.1.3/failure/trait.Fail.html)を提供しており、独自エラー型でこれを実装します。
Failは自動実装の為のマクロが提供されているので、`#[derive(Fail)]`とするだけでほとんどの実装を行ってくれます。
*ほとんどの実装*というのは`Display` traitの実装が実は必須なのですが、これを自動では行ってくれないのです。
しかし簡単に`Display` traitの実装を用意できる機能が提供されています。
下記のコード中の`#[fail(display = "...", ...)]`の様に記述する事で後はfailure側で自動的に`Display` traitを実装を行ってくれます。

~~~~rust
#[macro_use]
extern crate failure;

use std::fs;
use std::io;

#[derive(Debug, Fail)]
enum MyError {
    // これでDisplayの実装を用意してくれる。
    // この構文の記法はprintln!マクロ等とほぼ同じで、
    // フォーマット文字列の後にはフィールド名を書きます。
    #[fail(display = "An IO error occured: {}", 0)]
    IoError(io::Error),
}

// Fromは自分で実装...
impl From<io::Error> for MyError {
    fn from(err: io::Error) -> MyError {
        MyError::IoError(err)
    }
}

// MyError型をエラー型として使えるようになった！
fn load_and_print() -> Result<(), MyError> {
    println!("{}", fs::read_to_string("/file_does_not_exist")?);
    Ok(())
}

fn main() {
    load_and_print().unwrap_or_else(|err| eprintln!("{}", err));
}
~~~~

これをビルドして実行すると次の様な出力になります。

~~~~text
An IO error occured: No such file or directory (os error 2)
~~~~

独自のエラー型を`?`オペレーターで返してエラーメッセージの表示まで出来ました。

failureのより詳しい使い方等は下記のドキュメントを参照してください。
https://boats.gitlab.io/failure/intro.html