3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

anyhowの公式ドキュメントを読む

Last updated at Posted at 2025-12-22

Backtraceの話から始まり、前回までを通してエラー型の基本知識を確認してきました。

今回はこれら基礎知識を踏まえた上で、 anyhow クレートのドキュメントを読み直し、まとめたいと思います。

anyhow系統のクレート1は、「特に細かくエラーを分類せず、とにかくanyhow雑に伝搬させたい」という時に使うものでしょう。前回最後に「(雑に)何かしらエラーを表すトレイトオブジェクト」として Box<dyn std::error::Error> を紹介し、 Result<(), Box<dyn std::error::Error>>main 関数の最後の返り値型に使うという話をしました。anyhow::Errorの説明を読んだ感じ、 "Error works a lot like Box<dyn std::error::Error>" とあるので今回はこの話の続きと捉えてよさそうです!

anyhow

トップページに紹介されている機能をまとめてみました!

各機能を軽く見ていきます!

anyhow::Error 型の提供

色々な機能が施された Box<dyn std::error::Error> の上位互換となる型です。

Box<dyn std::error::Error> も同様ですが、 std::error::Errorトレイトを実装している任意の型を内包することができます。

これの何が嬉しいかというと、ある関数の返り値型が Result<T, anyhow::Error> のようである時、 ? 演算子には From::from による型変換機能が内蔵されているため、 std::error::Error さえ実装されているエラーならば2テキトーに ? を付けることですべて同一に扱うことができます!

src/main.rs
fn main() -> Result<(), anyhow::Error> { // anyhow::Result<()> で良いがわかりやすさのため分けて表現
    let () = match 334 {
        0 => Err(anyhow::anyhow!("= 0"))?, // anyhow::Error 自体
        n @ 1..334 => {
            #[derive(thiserror::Error, Debug)]
            #[error("{0} < 334")]
            struct UnderError(usize);
            
            Err(UnderError(n))? // std::error::Error を実装した構造体
        },
        n @ 334.. => {
            Err(std::io::Error::other(format!("334 <= {n}")))? // 外部クレートのエラー
        }
    };
    
    Ok(())
}

上記の Err(...) において Err に包まれている型はすべて異なりますが、その差を意識することなく ? 演算子を利用できるわけです。

anyhow::Error のメソッド

クレートの中心に位置する型なだけあり、 anyhow::Error は結構メソッドが多いためここで軽くまとめておこうと思います。似た役割のメソッドは同じ行にまとめました。

メソッド 説明
new anyhow::Error を生成する。内包されるエラー型が Backtrace をprovideしない時、内部で Backtrace が作成されキャプチャされる
msg これに Display + Debug な型の値を与えるとそれをメッセージとして anyhow::Error が生成される。ただしバックトレース等は保存されない模様(保存したい場合は new を使う)。 anyhow::anyhow! マクロとほぼ等価だがこちらは関数として扱える( .map の引数に直接渡したりなどできる )という利便性がある
backtrace anyhow::Error が持つ std::backtrace::Backtrace の参照を取得
chain / root_cause 元となるエラーや Context のメソッドにより途中に含められた値のチェインを得る。 root_cause はその中でも一番最初の元となるエラーを返す
downcast / downcast_ref / downcast_mut Anyにあるそれらと同様、内包している元となったエラーや Context のメソッドにより途中に含められた値へのダウンキャストを提供する。
is こちらも Any 同様内包している元となったエラーやContext のメソッドにより途中に含められた値へとダウンキャスト可能かどうかを示す
context Context::contextError にも生やしたもの。 なぜ .with_context(...) はないのだろう...?
from_boxed / into_boxed_dyn_error Box<dyn std::error::Error> との相互変換を行うための関連関数・メソッド
reallocate_into_boxed_dyn_error_without_backtrace into_boxed_dyn_error と似ているがこちらはバックトレースが失効される模様

本当は試したことがないメソッドが色々あるので試したいところなのですが、色々忙しいので今回は見送りで...ただ見た感じユーザーが普段使いするものは少なく、エラーライブラリを作ったりする際に便利なものという位置づけのようですね。ユーザーが普段使いすることがあるメソッドは new ぐらいではないでしょうか?

anyhow::Context トレイトによる anyhow::Error へのコンテキスト付与

anyhow名物 anyhow::Context は、 Result 型や Option 型に .context(...) (引数即時評価)メソッドや .with_context(|| ...) (引数遅延評価) メソッドを提供します。これらのメソッドを利用することでエラーに背景contextを付与できます!

ちょっとしたハックなのですが、line!() マクロ (や複数ファイルに分かれるなら file!() マクロ) を利用し、すべての ?.with_context(|| ...) を挟めるとエラートレースもどきが得られます。

Rust
use anyhow::Context;

fn func1() -> anyhow::Result<()> {
    Err(anyhow::anyhow!("error!"))
}

fn func2() -> anyhow::Result<()> {
    func1().with_context(|| line!())?;

    Ok(())
}

fn func3() -> anyhow::Result<()> {
    func2().with_context(|| line!())?;

    Ok(())
}

fn main() -> anyhow::Result<()> {
    func3().with_context(|| line!())?;
    
    Ok(())
}
実行結果
Error: 20

Caused by:
    0: 14
    1: 8
    2: error!

std::backtrace::Backtrace があんまり好きくなかった筆者はこの方法を多用していました。

そしてこれがhooq属性マクロ誕生のきっかけだったりします。

詳しくは次の記事を読んでください :point_right: 【Rust】.context(...)を書くな【anyhow・eyre】 #hooq - Qiita

anyhow::Error から元のエラーなどへのダウンキャスト

Any トレイトと似た感じで、 anyhow::Error の元になったエラーや、途中で付与したコンテキストへダウンキャストする機能が用意されています。

Rust
// マスキング(reduction)によりエラーが返っている場合は、
// オリジナルのコンテンツではなく代わりのコンテンツを返す
match root_cause.downcast_ref::<DataStoreError>() {
    Some(DataStoreError::Censored(_)) => Ok(Poll::Ready(REDACTED_CONTENT)),
    None => Err(error),
}

(公式ドキュメントより引用)

この match について、「anyhow::Error がトレイトオブジェクトである」点、そして「エラー型について動的ディスパッチ的に分岐している」点に注目すると、良くあるGCありクラスベースオブジェクト指向言語(Javaとか?)のtry-catchなどに近い仕組みに見えますね。網羅性を保てないためRustではこのハンドリング方法は微妙ですが、知っておくと何かに応用できるかもしれません。

バックトレース( std::backtrace::Backtrace )の提供

Box<dyn std::error::Error> からの最大の優位性はこの Backtrace が得られることでしょう。

RUST_BACKTRACE=1RUST_LIB_BACKTRACE=1 を設定して実行すると、 anyhow::Error 生成時点から勝手にバックトレースを生成してキャプチャしてくれます。

元となるエラーが Backtraceprovideしない場合、そこまでのバックトレースは存在しないことに気を付けましょう。provideにより anyhow::Error にそれまでのバックトレースを反映させる例は前回地味に記載しているので参考にしてみてください。( func5func4func1 の例)

RUST_BACKTRACE=1RUST_LIB_BACKTRACE=1 の使い分け・工夫

Backtrace解説回 でも言及しましたが(なぜか)anyhowクレートのドキュメントの方に同内容の紹介がありました!

  • パニック時・ Result によるエラーハンドリング時の両方でバックトレースが得たい: RUST_BACKTRACE=1
  • Result によるエラーハンドリング時だけ欲しい: RUST_LIB_BACKTRACE=1
  • パニック時だけ欲しい: RUST_BACKTRACE=1 かつ RUST_LIB_BACKTRACE=0

このようなスイッチングのために環境変数が2つ用意されています。

ユーティリティマクロの提供

楽に anyhow::Error を作ったりするためのユーティリティマクロがいくつか用意されています。軽く紹介!

  • anyhow!: 文字列スライス (&str) から楽に anyhow::Error を作れるマクロです。 format! マクロのようなフォーマット機能あり
  • bail!: return Err(anyhow!(...)); のショートハンドとなるマクロです。慣れると便利
  • ensure!: assert! マクロの親戚といった感じでしょうか?失敗時はパニックする代わりに anyhow::ResultErr を返します。
Rust
use anyhow::{anyhow, bail, ensure};

fn f(n: usize) -> anyhow::Result<()> {
    if n == 0 {
        return Err(anyhow!("0はダメ"));
    }
    
    if n == 4 {
        bail!("{n}は不吉!");
    }
    
    ensure!(n != 666);
    
    Ok(())
}

fn main() {
    println!("{:?} {:?} {:?} {:?}", f(0), f(4), f(666), f(42));
}
実行結果
Err(0はダメ) Err(4は不吉!) Err(Condition failed: `n != 666` (666 vs 666)) Ok(())

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=cc0da5bf476112e0ac52008bb4bdbc98

まとめ・所感

というわけで、hooqアドベントカレンダー 22日目の記事でした!

hooqはメソッドを ? 演算子にフックする属性マクロです。本アドカレではhooqの使い方を始め、hooqマクロを作成するにあたり得た知識、Rustのエラーハンドリング・エラーロギング周りの話をまとめています。

hooqマクロはanyhowクレートや次回取り組むeyreクレートとの相性も良いということは以下の記事にまとめています!もし良かったら読んでみてください :bow:

  1. anyhow以外だとeyreがあります。こちらは次回まとめます。

  2. 実際は std::error::Error + Send + Sync + 'static 、簡単に言えば通常の型かつ所有権がある状態というのが求められますが、細かいことはいいんだよ!!! Send + Sync + 'static を満たさない型を返そうとする人が悪いです

3
3
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
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?