tracingで?演算子の位置をログに出す【hooqマクロの紹介・使い所2選】
hooq属性マクロを作りました
2025年4月ごろから8カ月ほどかけて属性マクロクレートhooqを作ってきました!
そしてせっかく作ったhooqクレートの宣伝をすべくhooqアドベントカレンダーに25本の記事を書きました。
カレンダー記事執筆を通してRustのエラーハンドリング・ロギングにおけるhooqの立ち位置・強みを整理できたので、本記事では特にhooqマクロの強みを簡単に紹介したいと思います!
hooqマクロの概要
hooqは「 ? 演算子や return 、末尾式に、メソッドを挿入(フック)するマクロ」です!機能自体はシンプルだと思います。
フックするメソッドはユーザーで指定が可能です。以下は .map(|v| v * 2) を式と ? の間に挿入している例です。
use hooq::hooq;
#[hooq]
#[hooq::method(.map(|v| v * 2))]
fn double(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
let res = s.parse::<u32>()?;
Ok(res)
}
fn double_expanded(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
let res = s.parse::<u32>().map(|v| v * 2)?;
Ok(res)
}
#[test]
fn test() {
assert_eq!(double("21").unwrap(), double_expanded("21").unwrap());
}
上記のような例で使うことはほとんどなさそうですが、 エラーロギング用の処理をフックする のにとても便利です。
-
tracing::event!マクロを.inspect_err(|e| ...)で包んで?の前に挿入する -
anyhow::Context::with_contextを?の前に挿入する
冒頭例ではフックするメソッドを直接書いていましたがマクロ呼び出しごとに逐一書いていたのでは大変です。そのためフレーバーという仕組みを用意しています。このフレーバーは自分でカスタマイズしたものを用意することも可能です。その他マクロ利用を助ける便利機能や設定機能があるので、ぜひドキュメントをご覧いただきたいです!
次節からは先ほど紹介したエラーロギング例の詳細を解説します。
便利な使い所1: tracingで?演算子の位置をログに出す
直書きで ? の前にログを入れる方法を除けば、tracingで楽に ? 演算子の位置を出力する(調べた限り) 唯一の 方法です。
#[tracing::instrument(err)] では この属性マクロ自体がついている行 しか得られません。これは計装付与対象関数が複数の ? を内包している時に若干不利です。
ここを #[hooq::hooq(tracing)] #[tracing::instrument] に変える[1]と、hooqマクロの効果で ? 演算子の箇所にログが挿入され、 ? の位置を得られるようになります!
#[hooq::hooq(tracing)] が挿入するメソッドは(v0.3.1では[2])以下になります。
.inspect_err(|e| {
let path = ファイルパス;
let line = ?演算子がある行;
let col = ?演算子がある列;
let expr = 該当式を文字列化したもの;
::tracing::error!(
path,
line,
col,
error = %e,
expr,
);
})
以降、上記内容をPoCで示します。
以下に示しているのはよくあるバリデーションコードです。変換エラーが起きた際エラーログを送出するように #[tracing::instrument(err)] を付与しています。
impl TryFrom<(u32, ToDoItemInput)> for ValidatedToDoItem {
type Error = ValidationError;
#[tracing::instrument(err)]
fn try_from(
(
id,
ToDoItemInput {
title,
content,
started_at,
deadline_at: deadline,
},
): (u32, ToDoItemInput),
) -> Result<Self, Self::Error> {
let title = NonEmptyString::try_new(title.unwrap_or("untitled".to_string()))?;
let content = NonEmptyString::try_new(content.unwrap_or("default".to_string()))?;
let started_at = started_at.unwrap_or_else(Utc::now);
// deadlineが存在する場合、started_atより後であること
if let Some(deadline) = deadline
&& deadline <= started_at
{
return Err(ValidationError::new(
"deadline_at must be after started_at.",
));
}
Ok(ValidatedToDoItem {
id,
title,
content,
started_at,
deadline_at: deadline,
})
}
}
title と content のバリデーションがほぼ同じです。
let title = NonEmptyString::try_new(title.unwrap_or("untitled".to_string()))?;
let content = NonEmptyString::try_new(content.unwrap_or("default".to_string()))?;
それぞれでエラーになるように実行してみます。

title error with code.line.number = 34

content error with code.line.number = 34
code.line.number: 34 と表示され、どちらも同じエラーを返すため見分けがつかなくなってしまいました!これは辛いです。
ここで #[tracing::instrument(err)] を #[hooq::hooq(tracing)] #[tracing::instrument] に変えます! (err) 部分は Result::Err の時にイベント送出をしてくれる指定なのですが、それは今回はhooqの方が担うようになるので消しています。
impl TryFrom<(u32, ToDoItemInput)> for ValidatedToDoItem {
type Error = ValidationError;
- #[tracing::instrument(err)]
+ #[hooq::hooq(tracing)]
+ #[tracing::instrument]
fn try_from(
(
id,
ToDoItemInput {
title,
content,
started_at,
deadline_at: deadline,
},
): (u32, ToDoItemInput),
) -> Result<Self, Self::Error> {
let title = NonEmptyString::try_new(title.unwrap_or("untitled".to_string()))?;
let content = NonEmptyString::try_new(content.unwrap_or("default".to_string()))?;
let started_at = started_at.unwrap_or_else(Utc::now);
// deadlineが存在する場合、started_atより後であること
if let Some(deadline) = deadline
&& deadline <= started_at
{
return Err(ValidationError::new(
"deadline_at must be after started_at.",
));
}
Ok(ValidatedToDoItem {
id,
title,
content,
started_at,
deadline_at: deadline,
})
}
}
再び、 title と content それぞれでエラーになるように実行してみます。

title error with line = 47

content error with line = 48
line が 47 と 48 で別々な結果となりました!成功です。
こんな感じで、 #[tracing::instrument] マクロだけでは達成できない「 ? 演算子の位置をログ出力する」という機能を実現しています!
hooqマクロ導入を控える理由があるとすれば「属性マクロであること」だと想定している[3]のですが、この使い方だけはそもそもtracingの属性マクロを利用しているので完全上位互換と言っても過言ではないでしょう[4]!
便利な使い所2: なんちゃってエラートレースを出す
- hooq Reference 日本語版 > フレーバー > anyhow
- 【Rust】anyhowで実行時エラー発生行やスタックトレース的なものを取得する方法2選 #hooq - Qiita
- 【Rust】.context(...)を書くな【anyhow・eyre】 #hooq - Qiita
筆者がhooqを作ろうと決意したきっかけのうちの一つは、 anyhow::Context::with_context をすべての ? 演算子の前に挿入する癖があったためでした。というわけで、もちろんそれを代行してくれる手段としてhooqマクロは最適です!
use hooq::hooq;
#[hooq(anyhow)]
fn func1() -> anyhow::Result<()> {
Err(anyhow::anyhow!("An error occurred in func1"))
}
#[hooq(anyhow)]
fn func2() -> anyhow::Result<()> {
func1()?;
Ok(())
}
#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
func2()?;
Ok(())
}
Error: [src/main.rs:16:12]
16> func2()?
|
Caused by:
0: [src/main.rs:10:12]
10> func1()?
|
1: [src/main.rs:5:5]
5> Err(anyhow::anyhow!("An error occurred in func1"))
|
2: An error occurred in func1
ログを出したい関数に #[hooq(anyhow)] を付ければ、こんな感じでなんちゃってエラーログを出せます!
この機能は本当に欲しかったもので、その証拠にhooqのCI等開発で利用しているスクリプトはcargo scriptなのですがそこでhooq自体を利用していたりします。
ただ世間一般的には RUST_BACKTRACE=1 でバックトレースを出す方が定石らしく、そのためtracingと異なりこちらは完全上位互換等として売り出すのは無理そうです。
とはいえ、color_eyreしかりmietteしかりエラートレースの"見た目"を推しているクレートをしばしば見かけます。皆さん結構見た目を気にするみたいですね。
しかしそうなると、明らかに hooqの出力するトレースはバックトレースより見やすい ためエラーロギングクレートの末席に加えてくれても良いだろうというのが筆者の今の気持ちだったりします。
まとめ・所感
以上、筆者的に「ここは絶対にhooqマクロの強みだ!」と考える使い方2選を紹介しました!他にも思いついている使い方がなくはないので、それもいずれ形にできたらなと思います。
hooqが気になった方はぜひ使ってみて、そして良かったらスターをいただけると大変励みになります🙇♂️🙇♂️🙇♂️
ここまで読んでいただきありがとうございました!
Discussion