前回はanyhowクレートについてまとめました。準公式クレート1であるthiserrorとanyhowのまとめを通して、Rustのエラーに関する基本事項は網羅できたと言っても過言ではないと思います。
とはいえ、2025年12月現在2、押さえておくべきエラー関係クレートはその他にもいくつかあるでしょう。全部ではないかもしれませんが、本記事では以下を取り上げます!
-
eyre
- およびその系列の color-eyre
- snafu
- miette
実はeyre以外は筆者は触ったことがなく、良い機会なので触ってみよう!という企画です。ではさっそく...
eyre
eyreクレートは、anyhowからフォークされたクレートです。
anyhowとの最大の違いはEyreHandlerトレイトという表示カスタム機能の提供がある点です。それ以外は一部リネームされていたりanyhowとの互換性のために提供されている型があったりしますがほぼ同じです!
EyreHandler トレイトを実装したハンドラビルダを set_hook 関数に登録しておくことで、表示のカスタムをフックすることができるようになります!
hooqとは関係ない set_hook ですが良い機会です、コラボさせてみましょう!
hooqの eyre フレーバーを利用すると ? と式の間に .wrap_err_with(|| ...) 3 がフックされるようになります。
現状、 .wrap_err_with(|| ...) で追加されたコンテキストは最後にフックされたものから表示され原因は最後に表示されます。
use hooq::hooq;
#[hooq(eyre)]
fn func1() -> eyre::Result<()> {
let _ = std::fs::read_to_string("not_found.txt")?;
Ok(())
}
#[hooq(eyre)]
fn func2() -> eyre::Result<()> {
func1()?;
Ok(())
}
#[hooq(eyre)]
fn func3() -> eyre::Result<()> {
func2()?;
Ok(())
}
#[hooq(eyre)]
fn main() -> eyre::Result<()> {
func3()?;
Ok(())
}
Cargo.toml
[package]
name = "eyre_pg"
version = "0.1.0"
edition = "2024"
[dependencies]
eyre = "0.6.12"
hooq = "0.3.1"
$ cargo run -q
Error: [src/main.rs:24:12]
24> func3()?
|
Caused by:
0: [src/main.rs:18:12]
18> func2()?
|
1: [src/main.rs:12:12]
12> func1()?
|
2: [src/main.rs:5:53]
5> std::fs::read_to_string("not_...txt")?
|
3: No such file or directory (os error 2)
Location:
src/main.rs:5:53
Backtraceの表示に寄せ、原因となるエラーが上、呼び出し元が下となるよう、 EyreHandler を利用して順番を逆にしてみます!
use eyre::EyreHandler;
use std::fmt;
pub struct Handler;
impl EyreHandler for Handler {
fn debug(
&self,
error: &(dyn std::error::Error + 'static),
f: &mut fmt::Formatter<'_>,
) -> fmt::Result {
if f.alternate() {
return core::fmt::Debug::fmt(error, f);
}
let mut error_strs = Vec::new();
error_strs.push(format!("{}", error));
let mut error = error;
while let Some(cause) = error.source() {
error_strs.push(format!("{}", cause));
error = cause;
}
error_strs.reverse();
write!(f, "{}", error_strs.join("\n"))?;
Ok(())
}
}
pub fn eyre_hooq_init() {
eyre::set_hook(Box::new(move |_| Box::new(Handler))).unwrap();
}
use hooq::hooq;
+mod handler;
+use handler::eyre_hooq_init;
#[hooq(eyre)]
fn func1() -> eyre::Result<()> {
let _ = std::fs::read_to_string("not_found.txt")?;
Ok(())
}
#[hooq(eyre)]
fn func2() -> eyre::Result<()> {
func1()?;
Ok(())
}
#[hooq(eyre)]
fn func3() -> eyre::Result<()> {
func2()?;
Ok(())
}
#[hooq(eyre)]
fn main() -> eyre::Result<()> {
+ eyre_hooq_init();
func3()?;
Ok(())
}
❯ cargo run -q
Error: No such file or directory (os error 2)
[src/main.rs:8:53]
8> std::fs::read_to_string("not_...txt")?
|
[src/main.rs:15:12]
15> func1()?
|
[src/main.rs:21:12]
21> func2()?
|
[src/main.rs:29:12]
29> func3()?
|
Backtraceっぽい表示に近づきました!どっちの方が好みですかね...?
ともかく、eyreはカスタムのエラーハンドリングクレートを作りたくなった時のカスタマイズ性はかなり良さそうです!
案外anyhowでも良いかも...?
eyreはanyhowからフォークされたというだけで、anyhowのメンテナンスは最近も普通に行われています。なんならeyreのdocs.rsを読むと(READMEではしっかり修正されているのに反して)eyreのそれはanyhowよりも少し古い情報を示していたりなど案外anyhowよりも少し抜けているところが多く、「絶対eyreの方が良い/新しい!」とは一概には言えない様相でした。
ただデフォルトで Location: \n src/main.rs:5:53 みたいな行情報を出してくれたのはeyreの方だけ でした!どういう原理なんだろう...Location::callerを使っているっぽい?ドキュメントに記載がないだけで細かい点を見ていくとeyreに軍配が上がる要素が色々あるかもしれません。ちゃんとドキュメントに載せてほしい...
後述のcolor-eyreによるBacktrace表示が好きならそちらを使うとよさそうです。そうではなく普通のeyreを使うぐらいなら、エラートレースが見やすいことに定評がある組み合わせanyhow + hooqもぜひ一度検討いただきたいです!(ダイマ)
color-eyre
eyreによる表示カスタマイズが施されたサブクレートで、デフォルトだとめちゃくちゃ読みにくいBacktraceをかなり読みやすくしてくれるクレートです。
RUST_LIB_BACKTRACE=full に抵抗がない バックトレースマニアならeyreではなくcolor-eyre一択です!
利用例 (長いので折り畳み)
use color_eyre::eyre;
fn func1() -> eyre::Result<()> {
let _ = std::fs::read_to_string("not_found.txt")?;
Ok(())
}
fn func2() -> eyre::Result<()> {
func1()?;
Ok(())
}
fn func3() -> eyre::Result<()> {
func2()?;
Ok(())
}
fn main() -> eyre::Result<()> {
color_eyre::install()?;
func3()?;
Ok(())
}
[package]
name = "color_eyre_pg"
version = "0.1.0"
edition = "2024"
[dependencies]
color-eyre = "0.6.5"
$ RUST_LIB_BACKTRACE=full cargo run -q
Error:
0: No such file or directory (os error 2)
Location:
src/main.rs:4
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⋮ 5 frames hidden ⋮
6: <core::result::Result<T,F> as core::ops::try_trait::FromResidual<core::result::Result<core::convert::Infallible,E>>>::from_residual::h8aacbe6dfd28838d
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:2177
2175 │ fn from_residual(residual: Result<convert::Infallible, E>) -> Self {
2176 │ match residual {
2177 > Err(e) => Err(From::from(e)),
2178 │ }
2179 │ }
7: color_eyre_pg::func1::h4b00c126e72ef062
at /home/namn/workspace/qiita_adv_articles_2025/programs/adv23/color_eyre_pg/src/main.rs:4
2 │
3 │ fn func1() -> eyre::Result<()> {
4 > let _ = std::fs::read_to_string("not_found.txt")?;
5 │
6 │ Ok(())
8: color_eyre_pg::func2::h1de555d8f029cf7f
at /home/namn/workspace/qiita_adv_articles_2025/programs/adv23/color_eyre_pg/src/main.rs:10
8 │
9 │ fn func2() -> eyre::Result<()> {
10 > func1()?;
11 │ Ok(())
12 │ }
9: color_eyre_pg::func3::h3a7272ee639c925a
at /home/namn/workspace/qiita_adv_articles_2025/programs/adv23/color_eyre_pg/src/main.rs:15
13 │
14 │ fn func3() -> eyre::Result<()> {
15 > func2()?;
16 │ Ok(())
17 │ }
10: color_eyre_pg::main::h31f64ef2a97b96df
at /home/namn/workspace/qiita_adv_articles_2025/programs/adv23/color_eyre_pg/src/main.rs:22
20 │ color_eyre::install()?;
21 │
22 > func3()?;
23 │ Ok(())
24 │ }
11: core::ops::function::FnOnce::call_once::h26087039812f18d3
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250
248 │ /// Performs the call operation.
249 │ #[unstable(feature = "fn_traits", issue = "29625")]
250 > extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
251 │ }
252 │
12: std::sys::backtrace::__rust_begin_short_backtrace::h6c94b51569f261d1
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:158
156 │ F: FnOnce() -> T,
157 │ {
158 > let result = f();
159 │
160 │ // prevent this frame from being tail-call optimised away
⋮ 14 frames hidden ⋮
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
利用については別記事でも行っているのでそちらを参照してください! ![]()
snafu
anyhow + thiserror + Backtrace といった感じのクレートでしょうか...?このクレートだけで色々できるというのがコンセプトみたいです!
-
Whatever型およびwhateverマクロ: anyhowっぽい -
#[derive(Debug, Snafu)]マクロ: thiserrorっぽい -
Backtrace: 思いっきりstd::backtrace::Backtrace
では早速触ってみます! .with_whatever_context(|e| ...) を付けないと Result<_, Whatever> で運用できなかったため思わずhooqを併用してしまいました...ちなみに今回試しませんでしたが行番号系統の情報を扱う機構 Location も用意されているようです。
[snafu]
method = """.with_whatever_context(|_| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
::std::format!("[{path}:{line}:{col}] \n{expr}")
})"""
use hooq::hooq;
use snafu::{Backtrace, ErrorCompat, Whatever, prelude::*};
#[derive(Debug, Snafu)]
#[snafu(display("Could not read file: {}", source))]
struct FileReadError {
source: std::io::Error,
path: String,
backtrace: Backtrace,
}
#[hooq(snafu)]
fn func1() -> Result<(), Whatever> {
let path = "not_found.txt";
let _ = std::fs::read_to_string(path).context(FileReadSnafu {
path: path.to_string(),
})?; // 実際は .with_whatever_context(|_| ...) が挿入される
Ok(())
}
#[hooq(snafu)]
fn func2() -> Result<(), Whatever> {
// 実際は func1().with_whatever_context(|_| ...)?;
// 他の箇所も同様
func1()?;
Ok(())
}
#[hooq(snafu)]
fn func3() -> Result<(), Whatever> {
func2()?;
Ok(())
}
#[hooq(snafu)]
#[snafu::report]
fn main() -> Result<(), Whatever> {
if let Err(e) = func3()
&& let Some(bt) = ErrorCompat::backtrace(&e)
{
eprintln!("Backtrace:\n{bt}");
}
func3()?;
Ok(())
}
Cargo.toml
[package]
name = "snafu_pg"
version = "0.1.0"
edition = "2024"
[dependencies]
hooq = "0.3.1"
snafu = "0.8.9"
$ SNAFU_RAW_ERROR_MESSAGES=1 cargo run -q
Backtrace:
0: snafu::backtrace_impl::<impl snafu::GenerateImplicitData for std::backtrace::Backtrace>::generate
at /home/namn/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/snafu-0.8.9/src/backtrace_impl_std.rs:5:9
... 省略 ...
4: snafu_pg::func3
at ./src/main.rs:30:12
5: snafu_pg::main::{{closure}}
at ./src/main.rs:37:21
6: snafu_pg::main
at ./src/main.rs:35:1
... 省略 ...
19: std::rt::lang_start
at /home/namn/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:205:5
20: main
21: <unknown>
22: __libc_start_main
23: _start
Error: [src/main.rs:43:12]
43> func3()?
|
Caused by these errors (recent errors listed first):
1: [src/main.rs:30:12]
30> func2()?
|
2: [src/main.rs:24:12]
24> func1()?
|
3: [src/main.rs:17:7]
15> std::fs::read_to_string(path).context(FileReadSnafu {
16| path: path.to_string(),
17| })?
|
4: Could not read file: No such file or directory (os error 2)
5: No such file or directory (os error 2)
このクレート欲張りすぎだろう!!ただ壮大な割には(たぶん筆者の書き方が悪いのですが)バックトレースの扱いが不明瞭だったり Whatever と #[derive(Debug, Snafu)] で定義した型とのシナジーがあるのかないのかよくわからず、要は学習コストが高そうなので「......こりゃあ『anyhow + thiserror』でよいですね」4と言われてしまっても致し方なさそうと個人的には思いました。
std::backtrace::Backtraceが1.65.0で安定化したり、nightlyでthiserrorに#[backtrace]という不活性属性が追加されたりといった動きはもしかしたらsnafuを意識してというのもあるのかもしれません。シランケド
snafuは規模が大きすぎるので別な機会があれば改めてまとめたいところです。
miette
今回最後に触ってみるのはこちらのmietteクレートです!
color-eyreのように、エラーの 出力 にフィーチャーしたクレートのようで、特にeyre以上に「表示をカスタマイズできる」ことを売りにしているみたいです。
-
std::error::Errorに機能追加したようなDiagnosticトレイトを提供-
std::error::Errorの実装自体はthiserror::Errorderiveマクロに行わせる設計らしい
-
-
#[derive(Diagnostic)]マクロの提供 -
anyhow::Error/eyre::Reportに代わるmiette::Errorの提供-
anyhow!/eyre!の代わりのmiette!マクロも提供
-
- eyreと似た
ReportHandler -
任意のソースコードのハンドリング
- 「ソースコードのスパン」といった概念搭載
- Rustで何かしらのDSLを実装する時に使えそう...?
こちらもsnafu同様複数の役割を兼任したクレートのようですね。eyre + thiserrorといったところでしょうか?ライブラリクレートとアプリクレート両面についてドキュメントで使い方を指南されています。
今回はとりあえずアプリクレート想定で触りました! .wrap_err_with(|| ...) を挟みたくなったので例によってhooqを使ってしまいました...
.into_diagnostic() は Result<_, miette::Error> に対しては呼べませんが( miette::Error が std::error::Error を実装しないため)、 .map_err(|e| miette::miette!(e)) なら Result<_, E> where E: std::error::Error からも Result<_, miette::Error> からも呼べたので、こちらを利用してます5。
[miette]
method = """.map_err(|e| ::miette::miette!(e))
.wrap_err_with(|| {
let file = $file;
let line = $line;
let column = $column;
let source = ::hooq::source_excerpt_helpers::pretty_stringify!($source);
::miette::diagnostic!("[{file}:{line}:{column}]\n{source}")
})"""
trait_uses = ["::miette::WrapErr"]
use hooq::hooq;
#[hooq(miette)]
fn func1() -> miette::Result<()> {
let _ = std::fs::read_to_string("not_found.txt")?;
Ok(())
}
#[hooq(miette)]
fn func2() -> miette::Result<()> {
func1()?;
Ok(())
}
#[hooq(miette)]
fn func3() -> miette::Result<()> {
func2()?;
Ok(())
}
#[hooq(miette)]
fn main() -> miette::Result<()> {
func3()?;
Ok(())
}
Cargo.toml
[package]
name = "miette_pg"
version = "0.1.0"
edition = "2024"
[dependencies]
hooq = "0.3.1"
miette = { version = "7.6.0", features = ["fancy"] }
実行結果:
実行結果 ALT
$ cargo run -q
Error: × [main.rs:24:12]
│ func3()?
├─▶ [main.rs:18:12]
│ func2()?
├─▶ [main.rs:12:12]
│ func1()?
├─▶ [main.rs:5:53]
│ std::fs::read_to_string("not_found.txt")?
╰─▶ No such file or directory (os error 2)
折角 ソースコード情報を表示してくれるクレート なのにその機能を有効活用できてない(hooqで無理やり表示してます)!!!!いつかここもきっちり取り組みたいですね...また、 Diagnostic deriveマクロを使ったライブラリクレートの例も出したかったですが、良い例が思いつかなかった & あんまり執筆時間が取れなかったのでこちらもまたいつか...
「 mietteの本領はソースコード表示の方にありそう 」ということが分かった一方で、その機能を軒並み触れなかったので不完全燃焼です ![]()
まとめ・所感
hooqアドベントカレンダー 23日目の記事でした!
hooqはメソッドを ? 演算子にフックする属性マクロです。本アドカレではhooqの使い方を始め、hooqマクロを作成するにあたり得た知識、Rustのエラーハンドリング・エラーロギング周りの話をまとめています。
どのクレートも地味にhooqとのシナジーが高そうだったのが印象的でした。まだこれらのクレートのフレーバーは用意していないのですが検討の余地ありですね!hooqもいつかこれらのクレートに肩を並べるくらい有名になってほしいものです。
今日まで投稿してきた記事で、2025年に存在するエラー関連クレート群については一通り紹介できたと思います!あと一つ6を除いて...
というわけで、まとめ切れるかはわかりませんが次回はtracingについてまとめたいと思います!ここまで読んでいただきありがとうございました!
-
Rust言語のリーダー的存在、dtolnay氏がホストしていれば公式みたいなものなので、これを基準に準公式と呼ぼうかって思ったのですが、eyreのコントリビューターもされているようで、何を基準に準公式と呼べばよいかモニョっています...まぁともかく少なくともthiserrorとanyhowについては公式みたいなものでしょう。 ↩
-
今回のクレート選定にはChatGPTに協力してもらい、自分がその中からさらに選定しました。次回書くtracing以外で「これも押さえておくべきだろう」というものがあればコメントください! ↩
-
なるべく新APIに寄り添った方が良いだろうと
WrapErr::wrap_err_withをフックする仕様にしており記事執筆時点ではそうなっていますが、Option型へのフックができなくなり大変不便なのでanyhow同様ContextCompat::with_contextの方をフックする方針に修正予定です。この修正は記事掲載のソースコードには影響ないはず。 ↩ -
......こりゃあ『マリオカート』でよいですね ...記事を読むとわかりますが良くない誤用です。だからこそここで言ったというか多分筆者が理解してないsnafuの良さがあると思うので誰かマサカリを投げてくれ ↩
-
.into_diagnostic()だらけになって大変!という事前情報を他記事様から得ていたのですが、hooqでこれを回避できて地味に嬉しいです。 ↩ -
tracingクレートだけでなくlogクレートも本アドベントカレンダーでは扱ってきませんでした。しかし今日ではlogクレートの代わりにtracingクレートを利用すればよくなってきている & 必要な知識も似通っているため、本アドカレではまとめないつもりです。 ↩
