🐈

anyhowでRustのエラーハンドリングメモ

2024/03/11に公開


(どうやらanyhowクレートを使うと簡単にエラーハンドリングできるらしい)
(Stringとstd系Errorの狭間で考えていた時間は徒労に終わった)

anyhowクレートとは

エラーハンドリング用のクレート(これ)です。
↓の記事がわかりやすかったのでこの記事だけでいいと思います。

使い方まとめ

自分用備忘リファレンスコードです。

use anyhow::{Result, Context, anyhow, bail, ensure};

fn main() -> Result<()> {
    err_handle_w_anyhow(&0)?;
    Ok(())
}
fn err_handle_w_anyhow(handle_type: &i32) -> Result<()> {
    let foo = "foo";
    let mut misc: Vec<String> = Vec::new();
    let conditional_expr = false;

    match *handle_type {
        1 => std::fs::read_to_string(foo).context("anyhow::Error is created with io::Error and this strings.")?,
        2 => std::fs::read_to_string(foo).with_context(|| format!("To avoid format cost etc., lazy eval is abled. {}", foo))?,
        3 => misc.pop().context("Option::None is casted to Result::Err.")?,
        _ => String::default(),
    };

    match *handle_type {
        4 => { return Err(anyhow!("Create anyhow::Error by manual like format: {}", foo)); },
        5 => { bail!("bail!({}) == return Err(anyhow!({}))", foo, foo); },
        6 => { ensure!(conditional_expr, "If conditional expr is false, bail!({})", foo); },
        _ => (),
    };
    Ok(())
}

// test with the command: cargo test -- --nocapture
#[cfg(test)]
mod tests{
    use crate::err_handle_w_anyhow;

    #[test]
    fn test_run() {
        for i in 1..7 {
            println!("---------- pattern {} ----------", i);
            if let Err(e) = err_handle_w_anyhow(&i) {
                println!("{:?}", e);
            }
        }
    }
}

テスト結果

vscode ➜ /workspaces/z_rust_sandbox (master) $ cargo test -- --nocapture
   Compiling z_rust_sandbox v0.1.0 (/workspaces/z_rust_sandbox)
    Finished test [unoptimized + debuginfo] target(s) in 0.17s
     Running unittests src/main.rs (target/debug/deps/z_rust_sandbox-9dcf870a13e0264d)

running 1 test
---------- pattern 1 ----------
anyhow::Error is created with io::Error and this strings.

Caused by:
    No such file or directory (os error 2)
---------- pattern 2 ----------
To avoid format cost etc., lazy eval is abled: foo

Caused by:
    No such file or directory (os error 2)
---------- pattern 3 ----------
Option::None is casted to Result::Err.
---------- pattern 4 ----------
Create anyhow::Error by manual like format: foo
---------- pattern 5 ----------
bail!(foo) == return Err(anyhow!(foo))
---------- pattern 6 ----------
If conditional expr is false, bail!(foo)
test tests::test_run ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

所感

今のところRustのエラーハンドリングのベストプラクティスは無い?ようですが、小規模アプリでResult<T, E>のEを何にすべきかとかスマートにエラーハンドリングうんぬん迷ったらanyhowで良さそうです。ライブラリの場合はthiserrorクレートを使うらしいです。

参考文献

Discussion