こんにチュア!本記事は hooqアドベントカレンダー 8日目の記事です!
hooqアドベントカレンダーでは筆者が作った属性マクロhooqの話題を提供してきました!
???「hooqマクロとか意味なくない?」
筆者「...だ、誰だ!!!8カ月も掛けて作ったマクロを『無意味だ』なんて言うやつは...!」
???「ウェーイ!最高にイカすエラーレポートハンドラー、color-eyre だぜ!俺っちhooqマクロクンが何したいのかイマイチわかんねーんだよね」
筆者「hooqマクロを関数に付与すれば、エラーが発生した時に 何行目でどの式にてエラーが発生したか をロギングできるんだ!」
use hooq::hooq;
#[hooq]
fn rec(i: i32) -> Result<(), String> {
if i <= 0 {
return Err(format!("i must be positive: i = {i}"));
}
rec(i - 1)?;
Ok(())
}
#[hooq]
fn main() -> Result<(), String> {
rec(2)?;
Ok(())
}
$ cargo run -q
[src/main.rs:6:9] "i must be positive: i = 0"
6> return Err(format!("i must be positive: i = {i}"))
|
[src/main.rs:9:15] "i must be positive: i = 0"
9> rec(i - 1)?
|
[src/main.rs:9:15] "i must be positive: i = 0"
9> rec(i - 1)?
|
[src/main.rs:16:11] "i must be positive: i = 0"
16> rec(2)?
|
Error: "i must be positive: i = 0"
color-eyre「でもそれ俺っち入れて RUST_LIB_BACKTRACE=full で環境変数設定して実行すれば良くない?」
use color_eyre::Result;
use color_eyre::eyre::bail;
fn rec(i: i32) -> Result<()> {
if i <= 0 {
bail!("i must be positive: i = {i}");
}
rec(i - 1)?;
Ok(())
}
fn main() -> Result<()> {
color_eyre::install()?;
rec(2)?;
Ok(())
}
$ RUST_LIB_BACKTRACE=full cargo run -q
Error:
0: i must be positive: i = 0
Location:
src/main.rs:6
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⋮ 6 frames hidden ⋮
7: project::rec::he0813d27dc0efcd9
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:6
4 │ fn rec(i: i32) -> Result<()> {
5 │ if i <= 0 {
6 > bail!("i must be positive: i = {i}");
7 │ }
8 │
8: project::rec::he0813d27dc0efcd9
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:9
7 │ }
8 │
9 > rec(i - 1)?;
10 │
11 │ Ok(())
9: project::rec::he0813d27dc0efcd9
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:9
7 │ }
8 │
9 > rec(i - 1)?;
10 │
11 │ Ok(())
10: project::main::h33fdf4be197c9e8b
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:17
15 │ color_eyre::install()?;
16 │
17 > rec(2)?;
18 │
19 │ Ok(())
11: core::ops::function::FnOnce::call_once::h6a3f6cda2169809d
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::h70f5fbbe40f8e036
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.
筆者「ぐぬぬ...」
併用例1: 簡単な例
筆者「しかし、 RUST_LIB_BACKTRACE=full を付けなければならないのは忘れがちだし、何よりcolor-eyreは表示がうるさい気がする...」
color-eyre「それは個人の感想じゃね?」
筆者「では読者に決めてもらおう!hooqは君とも併用可能なので、
-
RUST_LIB_BACKTRACE=fullがあるからcolor-eyreだけでいい - 環境変数を設定しなくても見やすいエラートレースが見れるからcolor-eyre & hooqがいい
この二択から決めてほしい!」
color-eyre「しゃーねーな!今回はhooqクンと共に使われてやるぜ!」
プログラム「で、俺が産まれたってわけ
↓」
Cargo.toml
[package]
name = "project"
version = "0.1.0"
edition = "2024"
[dependencies]
color-eyre = "0.6.5"
hooq = "0.3.1"
use color_eyre::Result;
use color_eyre::eyre::eyre;
use hooq::hooq;
#[hooq(color_eyre)]
fn rec(i: i32) -> Result<()> {
if i <= 0 {
// bail だとフックされないのでeyreを利用
return Err(eyre!("i must be positive: i = {i}"));
}
rec(i - 1)?;
Ok(())
}
#[hooq(color_eyre)]
fn main() -> Result<()> {
color_eyre::install()?;
rec(2)?;
Ok(())
}
color-eyre「実行結果はさらに派手になったぜ!正直過剰...引くわ~」
筆者「🥺」
$ RUST_LIB_BACKTRACE=full cargo run -q
Error:
0: [src/main.rs:21:11]
21> rec(2)?
|
1: [src/main.rs:12:15]
12> rec(i - 1)?
|
2: [src/main.rs:12:15]
12> rec(i - 1)?
|
3: [src/main.rs:9:9]
9> return Err(eyre!("i must be positive: i = {i}"))
|
4: i must be positive: i = 0
Location:
src/main.rs:9
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⋮ 6 frames hidden ⋮
7: project::rec::hd63f2707e105f278
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:9
7 │ if i <= 0 {
8 │ // bail だとフックされないのでeyreを利用
9 > return Err(eyre!("i must be positive: i = {i}"));
10 │ }
11 │
8: project::rec::hd63f2707e105f278
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:12
10 │ }
11 │
12 > rec(i - 1)?;
13 │
14 │ Ok(())
9: project::rec::hd63f2707e105f278
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:12
10 │ }
11 │
12 > rec(i - 1)?;
13 │
14 │ Ok(())
10: project::main::h40725e9dac4ec709
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-1/project/src/main.rs:21
19 │ color_eyre::install()?;
20 │
21 > rec(2)?;
22 │
23 │ Ok(())
11: core::ops::function::FnOnce::call_once::hd2f0f1f0dd1d79b9
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::h570170a081687a69
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.
color-eyre「最初のがhooqクンの出力で、後ろが俺っちな!」
併用例2: color-eyre公式例
color-eyre「俺っちはtracingクレートのSpanTraceとも相性ばっちりなんだけどその辺はどうなんよ?」
筆者「 #[tracing::instrument] とも併用可能です!」
color-eyre「じゃあいい機会だしもっと派手派手なエラー表示にしようぜ!」
筆者「公式の例を引っ張ってきました!」
Cargo.toml
[package]
name = "project"
version = "0.1.0"
edition = "2024"
[features]
default = ["capture-spantrace"]
capture-spantrace = []
[dependencies]
color-eyre = "0.6.5"
hooq = "0.3.1"
tracing = "0.1.43"
tracing-error = "0.2.1"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
use color_eyre::{Section, eyre::Report, eyre::WrapErr};
use hooq::hooq;
use tracing::{info, instrument};
#[hooq(color_eyre)]
#[instrument]
fn main() -> Result<(), Report> {
#[cfg(feature = "capture-spantrace")]
install_tracing();
color_eyre::install()?;
read_config()
}
#[cfg(feature = "capture-spantrace")]
fn install_tracing() {
use tracing_error::ErrorLayer;
use tracing_subscriber::prelude::*;
use tracing_subscriber::{EnvFilter, fmt};
let fmt_layer = fmt::layer().with_target(false);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("info"))
.unwrap();
tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default())
.init();
}
#[hooq(color_eyre)]
#[instrument]
fn read_file(path: &str) -> Result<(), Report> {
info!("Reading file");
std::fs::read_to_string(path).map(drop)
}
#[hooq(color_eyre)]
#[instrument]
fn read_config() -> Result<(), Report> {
read_file("fake_file")
.wrap_err("Unable to read config")
.suggestion("try using a file that exists next time")
}
$ RUST_LIB_BACKTRACE=full cargo run -q
2025-12-07T19:19:25.384248Z INFO read_config:read_file{path="fake_file"}: Reading file
Error:
0: [src/main.rs:13:5]
13> read_config()
|
1: [src/main.rs:44:5]
44> read_file("fake_file")
45| .wrap_err("Unab..nfig")
46| .suggestion("try ..time")
|
2: Unable to read config
3: [src/main.rs:38:5]
38> std::fs::read_to_string(path).map(drop)
|
4: No such file or directory (os error 2)
Location:
src/main.rs:38
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SPANTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
0: project::read_file with path="fake_file"
at src/main.rs:35
33 │
34 │ #[hooq(color_eyre)]
35 > #[instrument]
36 │ fn read_file(path: &str) -> Result<(), Report> {
37 │ info!("Reading file");
1: project::read_config
at src/main.rs:42
40 │
41 │ #[hooq(color_eyre)]
42 > #[instrument]
43 │ fn read_config() -> Result<(), Report> {
44 │ read_file("fake_file")
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ BACKTRACE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⋮ 4 frames hidden ⋮
5: <E as eyre::context::ext::StdError>::ext_report::h579e73a738e9c80c
at /home/namn/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/eyre-0.6.12/src/context.rs:26
24 │ D: Display + Send + Sync + 'static,
25 │ {
26 > Report::from_msg(msg, self)
27 │ }
28 │ }
⋮ 1 frame hidden ⋮
7: project::read_file::hc03983ead05337e6
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-usage/project/src/main.rs:38
36 │ fn read_file(path: &str) -> Result<(), Report> {
37 │ info!("Reading file");
38 > std::fs::read_to_string(path).map(drop)
39 │ }
40 │
8: project::read_config::h918a55c09a4a20e6
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-usage/project/src/main.rs:44
42 │ #[instrument]
43 │ fn read_config() -> Result<(), Report> {
44 > read_file("fake_file")
45 │ .wrap_err("Unable to read config")
46 │ .suggestion("try using a file that exists next time")
9: project::main::ha2bf25fdcdfcf12e
at /home/namn/workspace/qiita_adv_articles_2025/programs/hooq-color-eyre-usage/project/src/main.rs:13
11 │ color_eyre::install()?;
12 │
13 > read_config()
14 │ }
15 │
10: core::ops::function::FnOnce::call_once::h79582e7ef8b22a23
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 │
11: std::sys::backtrace::__rust_begin_short_backtrace::h8748ba3420932fa5
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 ⋮
Suggestion: try using a file that exists next time
Run with COLORBT_SHOW_HIDDEN=1 environment variable to disable frame filtering.
筆者「ログマシマシニンニクアブラプログラム完成...🤓」
まとめ
というわけでhooqをcolor-eyreクレートと合わせてみる例を紹介しました!
...皆さんはどちらの方法でバックトレースを得るのが好みでしょうか...?今回併用する例を書いてみたのは「どっちが良い」ということを白黒はっきりさせたかったのではなく、あくまでもhooqがどれぐらい他のクレートと遜色ないかや、新しい選択肢を提供できているかを試したかったためです。体裁的な意味ではそれらしいのではないでしょうか...?
hooqの立ち位置というものはもちろんcolor-eyreとのそれを確認したところでまだ不明瞭な部分が多いです。とりあえずanyhow・color-eyreについては確認できたので、次はtracingとの詳細比較をやってみたいですね。
ここまで読んでいただきありがとうございました!