Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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();
}

  1. Procedural macros lack rich reflection; we cannot type‑infer Result.