anyhowユーザー向けeyre/miette入門
anyhow を活用している Rust プログラマー向けに、近年注目されているeyreとmietteについて分かりやすく解説します。それぞれの特長を比較しつつ、どの場面で使うべきかも提案します。
はじめに
3年前、こちらの記事でanyhowやthiserrorについて解説しました。それ以来、Rust プロジェクトにおいてanyhowは欠かせないクレートのひとつになっています。この4年間でanyhowに大きな機能追加はないものの、その信頼性と使いやすさは変わりません。一方、最近ではanyhowにインスパイアされた新しいクレート、eyreとmietteが注目を集めています。本記事では、それらを試してみた感想を交えつつ、anyhow ユーザーの視点から解説していきます。
eyre
とは?
eyre は anyhow をフォークして作られたクレートで、基本的なコンセプトはそのままに、拡張性とカスタマイズ性を追求しています。ちなみに、発音は「エアー」です。[1][2]
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つです。
- ソースコードのスニペット付きのcolorizedされたbacktrace
- tracingのSpan情報の表示(tracing使用時のみ)
- エラーに付加的な情報追加
一番下にSuggestionが表示された
anyhowとの性能差
eyreは非常に便利なクレートですが、便利さは性能とのトレードオフです。eyreはデフォルトでbacktraceをキャプチャします。backtraceのキャプチャは非常に重い処理なので、性能に影響がでます。
確かめるためにanyhowとeyreでエラーを投げるこんな感じの簡単なプログラムでベンチマークを取ってみます。
- 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はアプリケーション、ライブラリ両方の機能を提供しています。
Diagnostics
トレイト
mietteはリッチなエラーを提供するためにDiagnostic
というトレイト及び、ユーザー定義のエラー型にDiagnostic
を簡単に実装するためにderive macroを提供しています。thiserrorでいうところのstd::error::Error
トレイトとthiserror::Error
derive 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::Report
はFrom<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プロジェクトでは、どのクレートを使いますか? コメントで教えてください!
Discussion