Batch Apply Under a Module
hooq can be attached not only to functions but to items containing executable syntax.
Here, an “item” refers to Rust syntax elements that can appear directly inside a module/scope: functions, mod, impl blocks, trait blocks, etc. (See syn::Item.) Items without executable syntax are irrelevant.
When a mod { ... } (brace form, not mod xxx;) is annotated with #[hooq], inner functions can be hooked without annotating each function.
This recursive behavior is not limited to mod. If functions/closures nest, hooq will greedily hook according to settings for inner functions/closures as well. Use #[hooq::skip_all] if compilation fails or you want to disable in a region.
Puzzle example below illustrating which inner values get hooked:
use hooq::hooq;
#[hooq] // attribute macro root exist only here
#[hooq::method(.inspect_err(|_| {}))]
mod hoge {
pub fn bar() -> i32 {
// NOT hook target because this function does not return Result
42
}
pub fn failable<T>(val: T) -> Result<T, ()> {
// The return type of this function is Result
// but "Ok" is specified as ignore_tail_expr_idents by default
// so NOT hook target
Ok(val)
}
#[hooq::tail_expr_idents("Ok", "Err")]
pub fn _failable_2<T>(val: T) -> Result<T, ()> {
if failable(false).unwrap() {
// ↑ above is NOT hook target
// ↓ below: hook target because the return type of this function is Result
return failable(val);
}
// hook target because "Ok" is now specified as tail_expr_idents
Ok(val)
}
pub fn fuga() -> Result<(), ()> {
// hook target because of `?`
failable(())?;
let _ = || {
// hook target because of `?`
failable(())?;
// hook target because of `?`
if failable(false)? {
// hook target because "Err" is specified as tail_expr_idents by default
return Err(());
}
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Ok(())
};
let _ = || {
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Result::<(), ()>::Ok(())
};
let _ = {
let _ = bar();
// hook target because "Err" is specified as tail_expr_idents by default
// even if this is the tail expression of the block not a closure
Result::<(), ()>::Err(())
};
let _ = || -> Result<(), ()> {
// hook target because hooq can know this closure returns Result
failable(())
};
let _ = || {
// NOT hook target because hooq cannot know this closure returns Result
failable(())
};
// NOT hook target because "Ok" is specified as ignore_tail_expr_idents by default
Ok(())
}
}
fn main() {
let _ = hoge::bar();
let _ = hoge::failable(123);
let _ = hoge::fuga();
}
Expansion shows aggressive yet fairly natural hooking for a macro1:
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use hooq::hooq;
mod hoge {
pub fn bar() -> i32 {
42
}
pub fn failable<T>(val: T) -> Result<T, ()> {
Ok(val)
}
pub fn _failable_2<T>(val: T) -> Result<T, ()> {
if failable(false).unwrap() {
return failable(val).inspect_err(|_| {});
}
Ok(val).inspect_err(|_| {})
}
pub fn fuga() -> Result<(), ()> {
failable(()).inspect_err(|_| {})?;
let _ = || {
failable(()).inspect_err(|_| {})?;
if failable(false).inspect_err(|_| {})? {
return Err(()).inspect_err(|_| {});
}
Ok(())
};
let _ = || { Result::<(), ()>::Ok(()) };
let _ = {
let _ = bar();
Result::<(), ()>::Err(()).inspect_err(|_| {})
};
let _ = || -> Result<(), ()> { failable(()).inspect_err(|_| {}) };
let _ = || { failable(()) };
Ok(())
}
}
fn main() {
let _ = hoge::bar();
let _ = hoge::failable(123);
let _ = hoge::fuga();
}
-
Procedural macros lack rich reflection; we cannot type‑infer
Result. ↩