LoginSignup
9
2

Rust: thiserrorでanyhow::Errorを手軽に隠蔽する方法

Posted at

anyhowはエラー生成が便利で、開発速度にメリットがあるため初動で使われることが多いです。しかし、ResultErranyhow::Errorを指定して露出させてしまうと、将来的な変更がしにくくなります。例えば、anyhowをやめてカスタムエラーに変更したくなったときにインターフェースの互換性が失われる恐れがあります。この問題を解決するために、thiserror#[error(transparent)]を用いてanyhowを隠蔽する方法を紹介します。これにより、anyhowのエラー生成の手軽さと、将来の互換性を壊しにくさを両立できます。

まず、Cargo.tomlに以下の依存関係を追加します。

[dependencies]
thiserror = "1.0.40"
anyhow = { version = "1.0.70", features = ["backtrace", "std"] }

anyhowbacktracestd のfeaturesを有効にすることで、バックトレース情報をサポートし、エラーハンドリングで標準ライブラリのエラーも扱えるようになります。

以下のサンプルコードでは、thiserror を用いて MyError 構造体を定義し、その中に anyhow::Error を隠蔽しています。

fn main() {
    let err: Result<(), MyError> = stack1();
    eprintln!("{err:?}");
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
struct MyError(anyhow::Error);

fn stack1() -> Result<(), MyError> {
    stack2()?;
    Ok(())
}

fn stack2() -> Result<(), MyError> {
    parse_int()?;
    Ok(())
}

fn parse_int() -> Result<i32, MyError> {
    "invalid"
        .parse::<i32>()
        .map_err(|e| e.into()) // ParseIntErrorをanyhow::Errorに変換する。このタイミングでバックトレースが付与される。
        .map_err(MyError) // anyhow::ErrorをMyErrorに変換する
}

プログラムを実行するために、以下のコマンドを使います。

RUST_BACKTRACE=1 cargo run

実行結果は以下のようになります。

Err(MyError(invalid digit found in string

Stack backtrace:
   ...
   6: my_crate::parse_int::{{closure}}
             at ./src/main.rs:23:22
   ...
   8: my_crate::parse_int
             at ./src/main.rs:21:5
   9: my_crate::stack2
             at ./src/main.rs:16:5
  10: my_crate::stack1
             at ./src/main.rs:11:5
  11: my_crate::main
             at ./src/main.rs:2:36
   ...

この方法で、MyError を使ってカスタムエラーに簡単に変更でき、将来のコード変更に備えることができます。また、anyhowのエラー生成の利便性も維持されています。

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2