Expand description
This is sub-crate for hooq crate. Please use hooq crate instead of using this crate directly.
hooq
A simple macro that inserts (hooks) a method before question operator (`?`).
?🪝 The name hooq comes from combining ‘HOOk’ and the ‘Question mark operator ( ? )’. 🪝?
Enhance your questions by hooq!?
Keywords: Result, Option, hook, Result hook, Option hook, ? hook, question hook, error, logging
Documentations:
- tutorial: https://anotherhollow1125.github.io/hooq/latest/en/tutorial/index.html
- reference: https://anotherhollow1125.github.io/hooq/latest/en/reference/index.html
- docs.rs: https://docs.rs/hooq/latest/hooq/
[!NOTE] 日本語版ドキュメントはこちら: docs/ja/README.md
Insert methods specified with #[hooq::method(...)] between expressions and the ? operator (Question Operator)!
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());
}You don’t have to explicitly specify #[hooq::method(...)]—there’s also a mechanism called “flavors” for easily applying pre-configured settings!
§Why hooq?
Being able to insert a method before the ? operator lets you reduce boilerplate for debugging and logging. As a result, you can increase logging information without sacrificing readability.
The Context::with_context method from the anyhow crate is a prime example. The hooq crate provides a flavor (anyhow) to make inserting this method easy.
use hooq::hooq;
#[hooq(anyhow)]
fn func1() -> anyhow::Result<i32> {
Err(anyhow::anyhow!("Error in func1"))
}
#[hooq(anyhow)]
fn func2() -> anyhow::Result<i32> {
func1()
}
#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
func2()?;
Ok(())
}.with_context(|| {...}) gets hooked, and the output looks like this:
Error: [mdbook-source-code/flavor-anyhow/src/main.rs:15:12]
15> func2()?
|
Caused by:
0: [mdbook-source-code/flavor-anyhow/src/main.rs:10:5]
10> func1()
|
1: [mdbook-source-code/flavor-anyhow/src/main.rs:5:5]
5> Err(anyhow::anyhow!("Error in func1"))
|
2: Error in func1Other concrete examples would make the README too long, so they have been compiled in the mdBook: Why use hooq?
Applying #[hooq] (or #[hooq(anyhow)]) to all functions yields information close to an error stack trace.
Below is a comparison table with other ways to obtain stack-trace-like information.
Backtrace | tracing | hooq | |
|---|---|---|---|
| Learning cost & flexibility | ⚠️ | ⚠️ | 🌈 |
| Ease of type definitions | ⚠️ | ✅ | ✅ |
| Macro-less | 🌈 | ❌ | ❌ |
| Information control | ⚠️ | ✅ | 🌈 |
| Platform support | ⚠️ | ✅ | 🌈 |
Legend:
- 🌈: Excellent
- ✅: Good
- ⚠️: Not so good
- ❌: Poor
Explanations:
- Learning cost & flexibility
- ⚠️
Backtracerequires defining the environment variableRUST_LIB_BACKTRACE=1, and because it depends on OS threads you need knowledge outside pure Rust control flow. - ⚠️
tracingcan be overkill if your only goal is a stack-trace-like overview. If you are already comfortable with it, it is a reasonable option. - 🌈
hooqonly needs attaching an attribute macro to function heads.
- ⚠️
- Ease of type definitions
- ⚠️ When combining
Backtracewith crates likethiserror, you must include the backtrace field up front. The more granular your error types, the harder retrospective addition becomes, or the less simple the error representation stays. - ✅
tracingimposes no particular constraints here. - ✅
hooqworks smoothly with any error handling crate.
- ⚠️ When combining
- Macro-less
- 🌈
Backtracedoes not rely on macros, which feels lightweight. - ❌ To conveniently obtain stack-trace-equivalent info with
tracing, using#[tracing::instrument(err)](or similar) is almost mandatory. - ❌
hooqis an attribute macro crate; if you prefer to avoid macros entirely, it is not suitable.
- 🌈
- Information control
- ⚠️ The raw output of
Backtraceis often too verbose 😵. In async contexts raw frames can be nearly useless and frequently excessive.- Crates like
color-eyrecan improve formatting and make it more practical.
- Crates like
- ✅
tracingcan follow functions even across async boundaries; however, to know details such as “which line’s?operator?” you must add manual logging. - 🌈
hooq(similar to#[tracing::instrument]) traces only functions annotated with#[hooq], giving you precise, opt‑in coverage. It can capture exact positions of?,return, and tail expressions for finer granularity.- Being an attribute macro you can conditionally attach it only in tests or behind features (
#[cfg_attr(..., hooq(...))]). - 💡 Can be combined with
tracingto increase information granularity. See the flavor docs fortracingin the mdBook.
- Being an attribute macro you can conditionally attach it only in tests or behind features (
- ⚠️ The raw output of
- Platform support
- ⚠️
Backtracemay be unavailable or partial on some platforms (see official docs: https://doc.rust-lang.org/std/backtrace/index.html#platform-support). - ✅ Ordinary
tracinglogging use cases typically face few platform restrictions. - 🌈
hooqmerely inserts a method before?(and optionallyreturn/ tail expressions), so it does not depend on platform-specific features. Creative usage per platform is possible.- 💡 For example
#[hooq::method(.unwrap()!)]can make?behave akin to a forced unwrap alias.
- 💡 For example
- ⚠️
§Documentation
For detailed usage instructions, please refer to the following! (Also included at the beginning, but repeated here)
- tutorial: https://anotherhollow1125.github.io/hooq/latest/en/tutorial/index.html
- reference: https://anotherhollow1125.github.io/hooq/latest/en/reference/index.html
- docs.rs: https://docs.rs/hooq/latest/hooq/
[!NOTE] 日本語版ドキュメントはこちら: docs/ja/README.md
§Install
Add it with cargo add as shown below,
cargo add hooqor add it to your Cargo.toml.
[dependencies]
hooq = "0.3.0"[!NOTE] MSRV is 1.88 due to
$linemeta variable line retrieval viaproc_macro::Span::line.
§Method inserted by default
If you don’t specify anything for #[hooq], the following method is inserted by default:
.inspect_err(|e| {
let path = $path;
let line = $line;
let col = $col;
let expr = ::hooq::summary!($source);
::std::eprintln!("[{path}:{line}:{col}] {e:?}\n{expr}");
})You can switch the method to hook with the inert attribute #[hooq::method(...)]. Also, when you specify a flavor at the macro call site such as #[hooq(log)] or #[hooq(anyhow)], the inserted method will change according to that flavor!
§Attributes Quick Reference
The hooq macro can modify its behavior using inert attributes such as #[hooq::method(...)].
See mdbook’s Attributes for more details!
| Name | Type | Description |
|---|---|---|
| flavor | Macro root meta | Apply settings for the specified flavor |
| trait_use | Macro root meta | Insert use XXX as _; before the item for the specified path (XXX) |
| method | Inert attribute | Set the method to insert (or expression to replace in replace mode) |
| skip_all / skip | Inert attribute | Disable hooking for expressions with this attribute |
| hook_targets | Inert attribute | Toggle hooking for ?, return, and tail expressions (default: all three) |
| tail_expr_idents | Inert attribute | Specify idents to hook when they appear in tail position (default: Err) |
| ignore_tail_expr_idents | Inert attribute | Specify idents to not hook even when they would be hooked (default: Ok) |
| result_types | Inert attribute | Specify return types for functions to hook return and tail expressions (default: Result) |
| hook_in_macros | Inert attribute | Specify whether to hook inside macros (default: true) |
| binding | Inert attribute | Create meta variables that are replaced with specified literals/expressions |
Usage example:
use hooq::hooq;
mod sub {
pub trait Trait {}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq(flavor = "hook", trait_use(sub::Trait))] // Attribute macro root.
#[hooq::method(.inspect_err(|_| { let _ = "error!"; }))] // All following attributes are inert.
#[hooq::hook_targets("?", "return", "tail_expr")]
#[hooq::tail_expr_idents("Err")]
#[hooq::ignore_tail_expr_idents("Ok")]
#[hooq::result_types("Result")]
#[hooq::hook_in_macros(true)]
#[hooq::binding(xxx = "xxx_value")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
#[hooq::skip_all]
if failable(false)? {
failable(())?;
}
#[hooq::skip]
if failable(false)? {
// Next line is not skipped.
failable(())?;
}
#[hooq::method(.inspect_err(|_| { let _ = $xxx; }))]
failable(())?;
Ok(())
}§Meta Variables Quick Reference
With method settings for insertion such as #[hooq::method(...)], you can use information convenient for debugging and logging through meta variables such as $line.
See mdbook’s Meta Variables for more details!
| Name | Literal Type | Description |
|---|---|---|
$line | usize integer | Line number where the hook target is located |
$column or $col | usize integer | Column number where the hook target is located |
$path | string | Relative path to the file containing the hook target |
$file | string | Name of the file containing the hook target |
$source | expression | Expression for insertion/replacement target used for debugging/logging (note the difference from $expr) |
$count or $nth | string | Indicates which replacement target this is |
$fn_name or $fnname | string | Name of the function containing the hook target |
$fn_sig or $fnsig | string | Signature of the function containing the hook target |
$xxx (example) | (arbitrary) | User-defined meta variable via inert attribute #[hooq::xxx = ...] |
$bindings or $vars | HashMap | All meta variable bindings |
$hooq_meta or $hooqmeta | hooq::HooqMeta | Struct combining $line, $col, $path, $file, $source, $count, and $bindings |
$expr | expression | Expression for replacement target used for replacement (note the difference from $source) |
$so_far or $sofar | expression | Hook set so far, mainly used for insertion |
Usage example:
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::xxx = "user defined binding."]
#[hooq::method(.inspect_err(|_| {
// Fundamental information provided by hooq.
let _line = $line;
let _column = $column;
let _path = $path;
let _file = $file;
let _source = stringify!($source);
let _count = $count;
let _fn_name = $fn_name;
let _fn_sig = $fn_sig;
// Meta vars defined by user.
let _xxx = $xxx;
let _bindings = $bindings;
// All information summarized up to this point.
let _hooq_meta = $hooq_meta;
}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
Ok(())
}§Built-in Flavors Quick Reference
Configuring settings with attributes every time is tedious. hooq has a feature called “flavors” that allows you to pre-configure settings.
Flavors can be user-defined, and hooq also provides several built-in ones!
See mdbook’s Flavors for more details! Below are the pre-configured flavors (built-in flavors).
| Flavor Name | Feature | Description |
|---|---|---|
| default | - | The flavor used when nothing is specified. Can be overridden with hooq.toml |
| empty | - | A flavor for when you don’t want to hook anything. Cannot be overridden |
| hook | - | Inserts a hook method taking hooq::HooqMeta as an argument. Intended for use via user-defined traits. Can be overridden |
| anyhow | anyhow | Inserts the with_context method. Can be overridden |
| eyre | eyre | Inserts the wrap_err_with method. Can be overridden |
| log | log | Inserts an inspect_err method calling ::log::error!. Can be overridden |
| tracing | tracing | Inserts an inspect_err method calling ::tracing::error!. Can be overridden |
Usage example:
use hooq::hooq;
#[hooq(anyhow)]
fn func1() -> anyhow::Result<i32> {
Err(anyhow::anyhow!("Error in func1"))
}
#[hooq(anyhow)]
fn func2() -> anyhow::Result<i32> {
func1()
}
#[hooq(anyhow)]
fn main() -> anyhow::Result<()> {
func2()?;
Ok(())
}Output example:
Error: [mdbook-source-code/flavor-anyhow/src/main.rs:15:12]
15> func2()?
|
Caused by:
0: [mdbook-source-code/flavor-anyhow/src/main.rs:10:5]
10> func1()
|
1: [mdbook-source-code/flavor-anyhow/src/main.rs:5:5]
5> Err(anyhow::anyhow!("Error in func1"))
|
2: Error in func1§License
This project is dual-licensed:
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
§Contributing
Issues and PRs are welcome!
Contribution guidelines are summarized on a separate page with the following topics:
- Snapshot testing
- CI
sync.rscommand- Languages (English/Japanese)
- Stance on using generative AI
Structs§
- Binding
Payload - Binding payload that contains the expression string and its value.
- Hooq
Meta - Metadata about the invocation of the
hooqmacro.