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

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

NameKindDescription
flavorroot metaApply settings from a named flavor.
trait_useroot metaInsert use XXX as _; before the item for the given trait path(s).
methodinertConfigure the method to insert/replace.
skip_all / skipinertSkip hooking the annotated expression; skip_all also skips inside it.
hook_targetsinertEnable hooking on ?, return, and/or tail_expr (default: all).
tail_expr_identsinertIdents to hook when appearing as tail/return values (default: Err).
ignore_tail_expr_identsinertIdents to avoid hooking even if otherwise eligible (default: Ok).
result_typesinertFunction return type idents considered hook targets for return/tail (default: Result).
hook_in_macrosinertWhether to hook inside macro invocations (default: true).
bindinginertDefine 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(...)]:

NameSyntax
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

NameSyntax
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 for return/tail).
  • Replacement mode: otherwise; replace the target using an expression. Use $expr to 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

NameSyntax
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.

NameSyntaxValues
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_all is present, do not hook (for skip, skip only in the same scope excluding children).
  • If target is inside a macro call (like println!(...) ) and hook_in_macros is false, do not hook.
  • For ?: hook if included in hook_targets.
  • For return: hook if included in hook_targets, and either ident is in tail_expr_idents or return type ident is in result_types and ident is not in ignore_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:

SyntaxNote
#[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(())
}