Hooq: A simple macro that hooks a method before question operator (`?`)

⚓ Rust    📅 2026-01-02    👤 surdeus    👁️ 1      

surdeus

I made a Rust attribute macro called hooq! It lets you “hook” a method call right before each ?.

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 :slight_smile: )

1 post - 1 participant

Read full topic

🏷️ Rust_feed