Rustのエラー処理の理解を深める

2021-11-13

なにこれ

Rustのエラー処理について理解を深めるために簡単なコードを書いていく。

モチベーション

以下のようなコードを書きたいときになんでanyhowを利用しているのかがよくわかっていないため。
複数種類のErrorをまとめて取り扱えるんでしょ?くらいの認識にしかなっていないので詳しく知りたい。

use dotenv::dotenv;

fn main() -> Result<(), dotenv::Error> {
    dotenv().ok();

    // main()はdotenv::Errorを返すが
    // read_string_from_dotenv()はstd::env::VarErrorを返す場合があるので、型が合わずにコンパイルエラーになる
    let test_string = read_string_from_dotenv()?;

    dbg!(test_string);
    Ok(())
}

fn read_string_from_dotenv() -> Result<String, std::env::VarError> {
    std::env::var("TEST_STRING")
}

まずはResult型のテストを行う

Rustではエラーが発生しうる箇所ではResultを利用する。try ctachのような機構は標準では無いらしい。

文字列を数値にパースする例を書いてみると以下のようになる。

#[cfg(test)]
mod tests {

    #[test]
    fn test_result() {
        assert_eq!("0".parse::<u8>(), Ok(0));
        assert_eq!("a".parse::<u8>().unwrap_err().kind(), &std::num::IntErrorKind::InvalidDigit);
    }
}

Okが帰ってくるときはサクッと書けるが、Errの場合はErrの詳細までテストしようとするとちょっとめんどい

複数種類のエラーを取り扱う

最初のコードで複数のエラーを受け取れるようにしてみる。

use dotenv::dotenv;

// 数種類のエラーを返す場合、Box<dyn std::error::Error>であれば受け付けられる
fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv().ok();

    let test_string = read_string_from_dotenv()?;

    dbg!(test_string);
    Ok(())
}

fn read_string_from_dotenv() -> Result<String, std::env::VarError> {
    std::env::var("TEST_STRING")
}

Box<dyn std::error::Error>とすれば受け付けられるようになる。これはなにをやっているのだろう?

Rustでは基本的にメモリにデータを格納する際にはスタック領域に割り当てられる。
Boxを利用するとヒープ領域に割り当てられるようになる。ヒープは明示的に解放するまで使い続けることができるメモリ領域。
dyn TraitTraitを満たす型を実行時にわかる情報(サイズなど)を用いる。このケースだとstd::error::Errorの実態わわからないのでdynを用いている。

anyhowを使ってみる

エラー処理にはanyhowを使うのが良いらしい。 早速使ってみましょう。

use dotenv::dotenv;

fn main() -> anyhow::Result<()> {
    dotenv().ok();

    let test_string = read_string_from_dotenv()?;

    dbg!(test_string);
    Ok(())
}

fn read_string_from_dotenv() -> Result<String, std::env::VarError> {
    std::env::var("TEST_STRING")
}

見た目もスッキリしますね。 anyhow::Result<T>core::result::Result<T, anyhow::Error>のエイリアスっぽいです。なのでErrorについては特に記述しなくても利用できる。
詳しいことまではわかっていませんが、anyhow::Errorが複数のエラーをまとめてくれるようです。
単純にResultのOkのときを意識して書けて、Errについてはまとめて取り扱えるようになるので多言語から来た自分としては扱いやすくなるなと感じた。

おわりに

あんまり難しいことはわからないですが、じっくり実験したり試したりして少し理解が深まった気がする。
いっぱいRust書いていこう。

リポジトリ