🔨

anyhowユーザー向けeyre/miette入門

2025/01/14に公開

anyhow を活用している Rust プログラマー向けに、近年注目されているeyreとmietteについて分かりやすく解説します。それぞれの特長を比較しつつ、どの場面で使うべきかも提案します。

はじめに

3年前、こちらの記事でanyhowやthiserrorについて解説しました。それ以来、Rust プロジェクトにおいてanyhowは欠かせないクレートのひとつになっています。この4年間でanyhowに大きな機能追加はないものの、その信頼性と使いやすさは変わりません。一方、最近ではanyhowにインスパイアされた新しいクレート、eyremietteが注目を集めています。本記事では、それらを試してみた感想を交えつつ、anyhow ユーザーの視点から解説していきます。

https://zenn.dev/yukinarit/articles/b39cd42820f29e

eyreとは?

eyre は anyhow をフォークして作られたクレートで、基本的なコンセプトはそのままに、拡張性とカスタマイズ性を追求しています。ちなみに、発音は「エアー」です。[1][2]

https://github.com/eyre-rs/eyre

anyhowとの比較

eyre は anyhow に近い API を持っていますが、一部機能に差異があります。以下に主な違いを比較します。

anyhow eyre
anyhow! eyre!
bail! bail!
ensure! ensure!
Error Report
Context::context WrapErr::wrap_err
Context::with_context WrapErr::wrap_err_with
Context::context for Option OptionExt::ok_or_eyre
Context::with_context for Option 無いのでこれで代替 Option::ok_or_else(|| eyre!(...))

Report Handlers

eyre の目玉機能は「Report Handlers」です。エラーや Backtrace の表示方法をランタイムにカスタマイズ可能です。主な公式ハンドラーは以下の通り:

stable-eyre: デフォルトのハンドラー(Backtrace サポートあり)
simple-eyre: 最小限の機能(Backtrace なし)
color-eyre: ソースコードやトレース情報を強調表示する高機能なハンドラー

color-eyre

eyreでおそらく最も重要な機能で、個人的にはeyre使うならcolor-eyre、逆にcolor-eyre使わないならeyre使う意味ないとまで言えます。color-eyreには主な機能は以下の3つです。

  1. ソースコードのスニペット付きのcolorizedされたbacktrace

  1. tracingのSpan情報の表示(tracing使用時のみ)

  1. エラーに付加的な情報追加


一番下にSuggestionが表示された

anyhowとの性能差

eyreは非常に便利なクレートですが、便利さは性能とのトレードオフです。eyreはデフォルトでbacktraceをキャプチャします。backtraceのキャプチャは非常に重い処理なので、性能に影響がでます。

確かめるためにanyhowとeyreでエラーを投げるこんな感じの簡単なプログラムでベンチマークを取ってみます。

https://github.com/yukinarit/playground/blob/03d2fa0467a21e9af3e498fa9f681b17ea9bb343/rust/error-management/benches/anyhow.rs#L3-L22

  • anyhow backtraceなし / source
    anyhow                  time:   [13.553 ns 13.757 ns 13.978 ns]
    
  • anyhow backtraceあり / source
    anyhow_with_backtrace   time:   [3.3945 µs 3.5365 µs 3.6954 µs]
    
  • eyre backtraceなし / anyhowの約2.3倍 / source
    eyre_without_backtrace  time:   [31.406 ns 31.861 ns 32.333 ns]
    
  • color-eyre full backtrace / anyhowの約1.91倍 / source
    eyre_with_backtrace     time:   [6.5067 µs 6.5405 µs 6.5786 µs]
    

やっぱり、full backtraceありではかなり遅いという結果になりましたね。性能が要求されるプログラムではこのトレードオフをよく考慮して使いましょう。

miette

mietteはanyhow, color-eyreにインスパイアされたエラー管理クレートです。発音はミエットです。anyhowやeyreが主にアプリケーションのエラー管理のみ特化しており、ライブラリのエラー管理はthiserror等の別クレートに任せているのに対し、mietteはアプリケーション、ライブラリ両方の機能を提供しています。

https://github.com/zkat/miette

Diagnosticsトレイト

mietteはリッチなエラーを提供するためにDiagnosticというトレイト及び、ユーザー定義のエラー型にDiagnosticを簡単に実装するためにderive macroを提供しています。thiserrorでいうところのstd::error::Errorトレイトとthiserror::Errorderive macroです。

以下のコードはmietteのリポジトリにあるserde_jsonを使ったコードサンプルですが、以下のようにエラーの型を定義し、serde_jsonからエラー発生場所の情報を定義したエラーの型に上手に渡すと、

#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error("malformed json provided")]
struct SerdeError {
    cause: serde_json::Error,
    #[source_code]
    input: String,
    #[label("{cause}")]
    location: SourceOffset,
}

impl SerdeError {
    /// Takes the input and the `serde_json::Error` and returns a SerdeError
    /// that can be rendered nicely with miette.
    pub fn from_serde_error(input: impl Into<String>, cause: serde_json::Error) -> Self {
        let input = input.into();
        let location = SourceOffset::from_location(&input, cause.line(), cause.column());
        Self {
            cause,
            input,
            location,
        }
    }
}

fn main() -> miette::Result<()> {
    let input = serde_json::to_string_pretty(&json!({
        "name": 123
    }))
    .into_diagnostic()?;

    let _library: Library =
        serde_json::from_str(&input).map_err(|cause| SerdeError::from_serde_error(input, cause))?;

    Ok(())
}

以下のようなリッチなエラー情報を表示することができます。素晴らしいですね。

into_diagnostic地獄

一方でデメリットもあります。anyhow/eyreと比べて大きな違いとしてはmiette::ReportFrom<E: std::error::Error>を実装していないことがあります。これにどんなデメリットがあるのかというと、anyhowやeyreでは以下のように?を使って書けていたコードが、

fn main() -> Result<()> {
    // do_stuff returns a `Result<T, E>` where E implements the `Error` trait.
    do_stuff().with_err("failed to do stuff")?;

    // or without with_err
    do_stuff()?;

    Ok(())
}

以下のようにすべての?の前にinto_diagnosticを書かなければならず、anyhowやeyreでのエラーハンドリングに慣れていた人にはかなりつらいと思います。

fn main() -> Result<()> {
    // do_stuff returns a `Result<T, E>` where E implements the `Error` trait.
    do_stuff().into_diagnostic().with_err("failed to do stuff")?;

    // or without with_err
    do_stuff().into_diagnostic()?;

    Ok(())
}

anyhowとの比較

mietteはanyhowと互換性のあるAPIを提供しています。

anyhow miette
anyhow! miette!
bail! bail!
ensure! ensure!
Error Report
Context::context WrapErr::wrap_err
Context::with_context WrapErr::wrap_err_with
Context::context for Option WrapErr::wrap_err
Context::with_context for Option WrapErr::wrap_err

anyhowとの性能差

backtraceではかなり遅いのに対してfull backtraceありではanyhowより早い結果となりました。なにか間違っていることがありましたら、誰か指摘ください。

  • miette backtraceなし / anyhowの約131倍 / source
    miette_without_backtrace time:   [1.7764 µs 1.7971 µs 1.8200 µs]
    
  • miette full backtrace / anyhowの約0.7倍 / source
    miette_with_backtrace   time:   [2.3801 µs 2.4791 µs 2.5866 µs]
    

機能まとめ

以下にanyhow, eyre, mietteの良さを雑にまとめてみました。

anyhow eyre miette
基本機能 🏆 🏆 🏆
Backtrace 🏆 🏆
よりリッチなBacktrace 🏆
SpanTree 🏆
性能 🏆
エラー付加情報 🏆 🏆
よりリッチなエラー付加情報 🏆

どれを使うべきか、私の結論

色々試してみた結果、私は以下のように考えています。

crate 特徴 オススメの利用シーン
anyhow シンプルで高速 何にでも。特に性能が要求されるプログラム、バックエンド等
eyre 美しいバックトレース、付加情報 アプリケーション、CLI、速度低下が許容できるバックエンド等
miette よりリッチなエラー表示が得意 アプリケーション、CLI、パーサー、プログラミング言語等

プロジェクトの性質に合わせて、最適なクレートを選びましょう。あなたのRustプロジェクトでは、どのクレートを使いますか? コメントで教えてください!

脚注
  1. 発音についての参考動画: YouTube ↩︎

  2. 名前の由来は小説「ジェーン・エア」かも? Wikipedia ↩︎

GitHubで編集を提案

Discussion