Hooq: A simple macro that hooks a method before question operator (`?`)
⚓ Rust 📅 2026-01-02 👤 surdeus 👁️ 1I made a Rust attribute macro called hooq! It lets you “hook” a method call right before each ?.
- GitHub - anotherhollow1125/hooq: A simple macro that inserts (hooks) a method before question operator (`?`). (はてな演算子 `?` の前にメソッドを挿入(フック)するシンプルなマクロ)
- crates.io: Rust Package Registry
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)
}
fn main() {
assert_eq!(double("21").unwrap(), double_expanded("21").unwrap());
}
It also supports ‘flavors’ (see docs):
The main use-cases I had in mind are:
1) Auto-inserting anyhow::Context::with_context
It can also auto-attach with_context-style error context at ? sites (so you get a richer error report).
use hooq::hooq;
#[hooq(anyhow)]
fn func1() -> anyhow::Result<i32> {
Err(anyhow::anyhow!("Error in func1"))
}
#[hooq(anyhow)]
fn func2() -> anyhow::Result<i32> {
let res = func1()?;
println!("{res}");
Ok(res)
}
#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
func2()?;
Ok(())
}
Error: [mdbook-source-code/flavor-anyhow/src/main.rs:19:12]
19> func2()?
|
Caused by:
0: [mdbook-source-code/flavor-anyhow/src/main.rs:10:22]
10> func1()?
|
1: [mdbook-source-code/flavor-anyhow/src/main.rs:5:5]
5> Err(anyhow::anyhow!("Error in func1"))
|
2: Error in func1
2) Auto-inserting tracing::error!
With plain tracing::error!, it’s hard to log the exact location of ? without hurting readability. Combined with hooq, you can keep the code clean and still log where the error is bubbling up.
use hooq::hooq;
use tracing::instrument;
#[hooq(tracing)]
#[instrument]
fn func1() -> Result<i32, String> {
Err("Error in func1".into())
}
#[hooq(tracing)]
#[instrument]
fn func2() -> Result<i32, String> {
println!("func2 start");
let res = func1()?;
println!("func2 end: {res}");
Ok(res)
}
#[hooq(tracing)]
#[instrument]
fn func3() -> Result<i32, String> {
println!("func3 start");
let res = func2()?;
println!("func3 end: {res}");
Ok(res)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let format = tracing_subscriber::fmt::format()
.with_ansi(false)
.without_time();
tracing_subscriber::fmt().event_format(format).init();
func3()?;
Ok(())
}
func3 start
func2 start
ERROR func3:func2:func1: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=7 col=5 error=Error in func1 expr="Err(\"Error in func1\".into())"
ERROR func3:func2: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=15 col=22 error=Error in func1 expr="func1()?"
ERROR func3: flavor_tracing: path="mdbook-source-code/flavor-tracing/src/main.rs" line=27 col=22 error=Error in func1 expr="func2()?"
Error: "Error in func1"
More details are in the docs:
By the way, I learned about std::backtrace::Backtrace after finishing this macro… (kidding)
I’d be happy if you give it a try!
(Please excuse me for making a similar post on Reddit as well.)
(I’m Japanese. Some of the link previews contain Japanese, but most of the documentation is in English, so please don’t be put off
)
1 post - 1 participant
🏷️ Rust_feed