Attributes
The behavior of the hooq macro is controlled by the root meta on #[hooq(...)] and by inert attributes inserted afterwards (#[hooq::attribute(...)]). This page explains configurable items and defaults.
Quick Reference
| Name | Kind | Description |
|---|---|---|
| flavor | root meta | Apply settings from a named flavor. |
| trait_use | root meta | Insert use XXX as _; before the item for the given trait path(s). |
| method | inert | Configure the method to insert/replace. |
| skip_all / skip | inert | Skip hooking the annotated expression; skip_all also skips inside it. |
| hook_targets | inert | Enable hooking on ?, return, and/or tail_expr (default: all). |
| tail_expr_idents | inert | Idents to hook when appearing as tail/return values (default: Err). |
| ignore_tail_expr_idents | inert | Idents to avoid hooking even if otherwise eligible (default: Ok). |
| result_types | inert | Function return type idents considered hook targets for return/tail (default: Result). |
| hook_in_macros | inert | Whether to hook inside macro invocations (default: true). |
| binding | inert | Define custom meta variable bindings (expression or literal). |
- Root meta: attributes written inside
#[hooq(...)]. - Inert attributes:
#[hooq::attribute(...)]placed in various positions after the macro.
Example with all attributes:
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(())
}
Expansion:
use hooq::hooq;
mod sub {
pub trait Trait {}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[allow(unused)]
use sub::Trait as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect_err(|_| {
let _ = "error!";
})?;
if failable(false)? {
failable(())?;
}
if failable(false)? {
failable(())
.inspect_err(|_| {
let _ = "error!";
})?;
}
failable(())
.inspect_err(|_| {
let _ = "xxx_value";
})?;
Ok(())
}
Root Meta
Two fields are available inside #[hooq(...)]:
| Name | Syntax |
|---|---|
| flavor | #[hooq(FLAVOR_NAME)] or #[hooq(flavor = "FLAVOR_NAME")] |
| trait_use | #[hooq(trait_use(PATH, ...))] or #[hooq(trait_uses(PATH, ...))] |
flavor
Apply base settings from a flavor:
use hooq::hooq;
#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
func()?;
Ok(())
}
#[hooq(my_flavor::sub_flavor)]
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq(flavor = "my_flavor.sub_flavor")]
fn func() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
Ok(())
}
hooq.toml example:
[my_flavor]
method = """.inspect(|_| {
let _ = $tag;
})"""
bindings = { tag = "\"my_flavor\"" }
[my_flavor.sub_flavor]
bindings = { tag = "\"my_flavor.sub_flavor\"" }
tail_expr_idents = ["Ok"]
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
func()
.inspect(|_| {
let _ = "my_flavor";
})?;
Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
.inspect(|_| {
let _ = "my_flavor.sub_flavor";
})
}
fn func() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect(|_| {
let _ = "my_flavor.sub_flavor";
})?;
Ok(())
.inspect(|_| {
let _ = "my_flavor.sub_flavor";
})
}
Sub‑flavors inherit and override parent flavor settings. Use dotted or path‑like names (e.g. base.sub or base::sub), and they can nest.
Further info, especially built‑ins like anyhow, writing hooq.toml, etc.: see Flavors.
trait_use
Insert #[allow(unused)] use PATH as _; above the item:
use hooq::hooq;
mod sub {
pub trait Inserted {
fn inserted(self) -> Self;
}
impl<T, E> Inserted for Result<T, E> {
fn inserted(self) -> Self {
self
}
}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq(trait_use(sub::Inserted))]
#[hooq::method(.inserted())]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
Ok(())
}
Expansion excerpt:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
mod sub {
pub trait Inserted {
fn inserted(self) -> Self;
}
impl<T, E> Inserted for Result<T, E> {
fn inserted(self) -> Self {
self
}
}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[allow(unused)]
use sub::Inserted as _;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(()).inserted()?;
Ok(())
}
Useful when the hooked method requires a trait (e.g. anyhow::Context for .with_context(...)). You can also configure trait_uses in hooq.toml so a flavor brings the necessary imports.
method
| Name | Syntax |
|---|---|
| method | #[hooq::method(...)] |
Set the method via inert attribute. Works both immediately under #[hooq] and inside the function.
use hooq::hooq;
#[hooq]
#[hooq::method(.inspect_err(|_| { let _ = "specified @ root"; }))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
#[hooq::method(.inspect_err(|_| { let _ = "specified @ inner"; }))]
failable(())?;
Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect_err(|_| {
let _ = "specified @ root";
})?;
failable(())
.inspect_err(|_| {
let _ = "specified @ inner";
})?;
Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
Two modes exist:
- Insertion mode: starts with
.; inserts between the expression and?(or at the end forreturn/tail). - Replacement mode: otherwise; replace the target using an expression. Use
$exprto access the original (already recursively hooked) expression.
Meta variables like $line, $source, $fn_name are available. Default flavor inserts:
.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}");
})
See Method and Meta Variables.
Skipping Hooks
| Name | Syntax |
|---|---|
| skip_all | #[hooq::skip_all] |
| skip | #[hooq::skip] |
skip_all
Prevent hooks inside the annotated expression:
use hooq::hooq;
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
#[hooq::skip_all]
let f = || -> Option<()> {
optional(())?; // If the hook is applied, an compile error occurs.
Some(())
};
let _ = failable(f())?;
Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn optional<T>(val: T) -> Option<T> {
Some(val)
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(()).inspect_err(|_| {})?;
let f = || -> Option<()> {
optional(())?;
Some(())
};
let _ = failable(f()).inspect_err(|_| {})?;
Ok(())
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn optional<T>(val: T) -> Option<T> {
Some(val)
}
skip
Skip only within the parent scope (children can still be hooked). Helpful when nested tail expressions would produce noisy logs.
use hooq::hooq;
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func1() -> Result<(), String> {
match failable(failable(()))? {
Ok(()) => Ok(()),
Err(s) => Err(s),
}
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func2() -> Result<(), String> {
#[hooq::skip]
match failable(failable(()))? {
Ok(()) => Ok(()),
Err(s) => Err(s),
} // Not hooked here.
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() {
let _ = func1();
let _ = func2();
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn func1() -> Result<(), String> {
match failable(failable(())).inspect_err(|_| {})? {
Ok(()) => Ok(()),
Err(s) => Err(s).inspect_err(|_| {}),
}
.inspect_err(|_| {})
}
fn func2() -> Result<(), String> {
match failable(failable(()))? {
Ok(()) => Ok(()),
Err(s) => Err(s).inspect_err(|_| {}),
}
}
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() {
let _ = func1();
let _ = func2();
}
Control Hook Targets
Inert attributes to tweak whether/how hooks apply.
| Name | Syntax | Values |
|---|---|---|
| hook_targets | #[hooq::hook_targets(...)] | Any of "?", "return", "tail_expr". |
| tail_expr_idents | #[hooq::tail_expr_idents(...)] | Idents like Err. |
| ignore_tail_expr_idents | #[hooq::ignore_tail_expr_idents(...)] | Idents like Ok. |
| result_types | #[hooq::result_types(...)] | Return type idents like Result. |
| hook_in_macros | #[hooq::hook_in_macros(...)] | true or false. |
Priority rules:
- If
skip_allis present, do not hook (forskip, skip only in the same scope excluding children). - If target is inside a macro call (like
println!(...)) andhook_in_macrosisfalse, do not hook. - For
?: hook if included inhook_targets. - For
return: hook if included inhook_targets, and either ident is intail_expr_identsor return type ident is inresult_typesand ident is not inignore_tail_expr_idents. - For tail: similar rules as
return.
hook_targets
You can specify whether to hook each of the following three types: the ? operator (Question Operator), return, and tail expressions (tail_expr). By default, all three types are hooked.
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("?")]
fn target_question() -> Result<(), String> {
failable(())?;
if failable(false)? {
return Err("error".into());
}
if failable(true)? {
Ok(())
} else {
Err("error".into())
}
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("return")]
fn target_return() -> Result<(), String> {
failable(())?;
if failable(false)? {
return Err("error".into());
}
if failable(true)? {
Ok(())
} else {
Err("error".into())
}
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::hook_targets("tail_expr")]
fn target_tail_expr() -> Result<(), String> {
failable(())?;
if failable(false)? {
return Err("error".into());
}
if failable(true)? {
Ok(())
} else {
Err("error".into())
}
}
fn main() {
let _ = target_question();
let _ = target_return();
let _ = target_tail_expr();
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn target_question() -> Result<(), String> {
failable(()).inspect_err(|_| {})?;
if failable(false).inspect_err(|_| {})? {
return Err("error".into());
}
if failable(true).inspect_err(|_| {})? { Ok(()) } else { Err("error".into()) }
}
fn target_return() -> Result<(), String> {
failable(())?;
if failable(false)? {
return Err("error".into()).inspect_err(|_| {});
}
if failable(true)? { Ok(()) } else { Err("error".into()) }
}
fn target_tail_expr() -> Result<(), String> {
failable(())?;
if failable(false)? {
return Err("error".into());
}
if failable(true)? { Ok(()) } else { Err("error".into()).inspect_err(|_| {}) }
.inspect_err(|_| {})
}
fn main() {
let _ = target_question();
let _ = target_return();
let _ = target_tail_expr();
}
tail_expr_idents
Idents to hook regardless of result_types. Default: Err.
Identifiers must be single idents (e.g., Zzz), not paths like xxx::yyy::Zzz.
Matching uses the last segment of a path (xxx::yyy::Zzz matches by Zzz).
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), String> {
let _: Result<(), String> = {
let _: Result<(), String> = {
let res = "error".to_string();
Err(res)
};
failable(())
};
#[hooq::tail_expr_idents("Err", "failable")]
let _: Result<(), String> = {
let _: Result<(), String> = {
let res = "error".to_string();
Err(res)
};
failable(()) // This will be hooked because of tail_expr_idents.
};
Ok(())
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() -> Result<(), String> {
let _: Result<(), String> = {
let _: Result<(), String> = {
let res = "error".to_string();
Err(res).inspect_err(|_| {})
};
failable(())
};
let _: Result<(), String> = {
let _: Result<(), String> = {
let res = "error".to_string();
Err(res).inspect_err(|_| {})
};
failable(()).inspect_err(|_| {})
};
Ok(())
}
ignore_tail_expr_idents
Avoid hooking these idents even when otherwise eligible. Default: Ok.
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() {
let f = || -> Result<(), String> { failable(()) };
#[hooq::ignore_tail_expr_idents("failable")]
let g = || -> Result<(), String> { failable(()) };
#[hooq::tail_expr_idents("!failable")]
let h = || -> Result<(), String> { failable(()) };
f().unwrap();
g().unwrap();
h().unwrap();
}
As shown above, you can also achieve the same behavior without ignore_tail_expr_idents by prefixing an ident with ! in tail_expr_idents (e.g., !Ok).
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() {
let f = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
let g = || -> Result<(), String> { failable(()) };
let h = || -> Result<(), String> { failable(()) };
f().unwrap();
g().unwrap();
h().unwrap();
}
If the same ident appears in both lists, it will be hooked due to simple mechanics; prefer using !Ident via tail_expr_idents instead.
result_types
Function return type idents considered for return/tail hooks. Default: Result.
As with tail_expr_idents, identifiers must be single idents (e.g., Zzz), not full paths like xxx::yyy::Zzz.
Matching uses the final segment of a path (xxx::yyy::Zzz matches by Zzz).
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
type MyResult = Result<(), String>;
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn func1() -> MyResult {
let _ = || -> Result<(), String> { failable(()) };
failable(())
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::result_types("MyResult")]
fn func2() -> MyResult {
// No longer hooked.
let _ = || -> Result<(), String> { failable(()) };
failable(())
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
#[hooq::result_types("Result", "MyResult")]
fn func3() -> MyResult {
let _ = || -> Result<(), String> { failable(()) };
let _ = || {
// Not hooked because return type of the closure is unknown.
failable(())
};
failable(())
}
fn main() {
let _ = func1();
let _ = func2();
let _ = func3();
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
type MyResult = Result<(), String>;
fn func1() -> MyResult {
let _ = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
failable(())
}
fn func2() -> MyResult {
let _ = || -> Result<(), String> { failable(()) };
failable(()).inspect_err(|_| {})
}
fn func3() -> MyResult {
let _ = || -> Result<(), String> { failable(()).inspect_err(|_| {}) };
let _ = || { failable(()) };
failable(()).inspect_err(|_| {})
}
fn main() {
let _ = func1();
let _ = func2();
let _ = func3();
}
hook_in_macros
Control hooking inside function‑like macros.
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::method(.inspect_err(|_| {}))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{}", failable("hello")?);
#[hooq::hook_in_macros(false)]
println!("{}", failable("world")?);
Ok(())
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
{
::std::io::_print(format_args!("{0}\n", failable("hello").inspect_err(|_| {})?));
};
{
::std::io::_print(format_args!("{0}\n", failable("world")?));
};
Ok(())
}
The arguments to function-like macros do not always follow standard Rust syntax, and hooking them incurs a small parsing cost. This inert attribute allows you to disable hooking inside macros. If you don’t need to hook inside macros, setting this to false may slightly reduce compile times.
binding
Define user meta variables (bindings). Several forms:
| Syntax | Note |
|---|---|
#[hooq::binding(xxx = ...)] | |
#[hooq::var(xxx = ...)] | |
#[hooq::xxx = ...] | xxx must not conflict with other inert attribute names. |
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
#[hooq]
#[hooq::method(.inspect_err(|_| { let _ = $xxx; }))]
#[hooq::xxx = 10]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
#[hooq::binding(xxx = "in block")]
{
failable(())?;
#[hooq::var(xxx = 42)]
failable(())?;
failable(())?;
}
failable(())?;
Ok(())
}
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect_err(|_| {
let _ = 10;
})?;
{
failable(())
.inspect_err(|_| {
let _ = "in block";
})?;
failable(())
.inspect_err(|_| {
let _ = 42;
})?;
failable(())
.inspect_err(|_| {
let _ = "in block";
})?;
}
failable(())
.inspect_err(|_| {
let _ = 10;
})?;
Ok(())
}
See also Meta Variables — Bindings.
Partial Application via Flavor
You can partially apply flavor settings via inert attributes:
#[hooq::attribute = flavor_name]for a single field.#[hooq::flavor = flavor_name]to override all inert‑attribute fields.#[hooq::bindings = flavor_name]to override existing user bindings.
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
type MyResult = Result<(), String>;
#[hooq]
#[hooq::method(.inspect_err(|_| {
let _x = $xxx;
let _y = $yyy;
}))]
#[hooq::xxx = "from root"]
#[hooq::yyy = "from root"]
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())?;
// Not hooked.
let _ = || -> MyResult { failable(()) };
#[hooq::method = "my_flavor"]
// Method will be changed.
failable(())?;
#[hooq::result_types = "my_flavor"]
// Hooked now.
let _ = || -> MyResult { failable(()) };
#[hooq::bindings = "my_flavor"]
// Bindings will be changed.
failable(())?;
#[hooq::flavor = "my_flavor"]
// All will be changed.
failable(())?;
Ok(())
}
hooq.toml:
[my_flavor]
method = """.inspect_err(|_| {
let _ = "from my_flavor";
let _x = $xxx;
let _y = $yyy;
})"""
result_types = ["Result", "MyResult"]
bindings = { xxx = "\"xxx from my_flavor\"", yyy = "\"yyy from my_flavor\"" }
Expansion:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
fn failable<T>(val: T) -> Result<T, String> {
Ok(val)
}
type MyResult = Result<(), String>;
fn main() -> Result<(), Box<dyn std::error::Error>> {
failable(())
.inspect_err(|_| {
let _x = "from root";
let _y = "from root";
})?;
let _ = || -> MyResult { failable(()) };
failable(())
.inspect_err(|_| {
let _ = "from my_flavor";
let _x = "from root";
let _y = "from root";
})?;
let _ = || -> MyResult {
failable(())
.inspect_err(|_| {
let _x = "from root";
let _y = "from root";
})
};
failable(())
.inspect_err(|_| {
let _x = "xxx from my_flavor";
let _y = "yyy from my_flavor";
})?;
failable(())
.inspect_err(|_| {
let _ = "from my_flavor";
let _x = "xxx from my_flavor";
let _y = "yyy from my_flavor";
})?;
Ok(())
}