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

Create / Use Presets via Flavors

At the end of the previous lesson we saw ok_or_else missing on Result when trying to hook uniformly. Can we hook something available on both Result and Option? The Context::with_context method from anyhow fits perfectly: on Option it converts None into an anyhow::Result::Err.

use anyhow::{Context, Result};
use hooq::hooq;

#[hooq]
#[hooq::method(.with_context(|| {
    let path = $path;
    let line = $line;
    let col = $col;
    let expr = ::hooq::summary!($source);

    format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
    let name = val.get("package")?.get("name")?.as_str()?;

    if name.contains("4") {
        return Err(anyhow::anyhow!(
            "name `{name}` contains '4'. Guido Mista disallow this."
        ));
    }

    println!("Mista「name: {name}」");

    Ok(())
}

#[hooq]
#[hooq::method(.with_context(|| {
    let path = $path;
    let line = $line;
    let col = $col;
    let expr = ::hooq::summary!($source);

    format!("[{path}:{line}:{col}]\n{expr}")
}))]
fn main() -> Result<()> {
    let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());

    let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;

    display_name_by_mista(&cargo_toml)?;

    Ok(())
}

Run to produce an error: the extra .with_context(...) calls accumulate, giving a trace.

Error: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:41:39]
  41>    display_name_by_mista(&cargo_toml)?
    |

Caused by:
    0: [mdbook-source-code/tutorial-4-with-anyhow/src/main.rs:17:9]
         17>    return Err(anyhow::anyhow!(
         18|            "name `{name}` contains '4'. Guido Mista disallow this."
         19|        ))
           |
    1: name `tutorial-4-with-anyhow` contains '4'. Guido Mista disallow this.

Because this is a frequent pattern hooq provides a preset—the anyhow flavor. Presets are called “flavors” in hooq. There are also flavors for log, eyre, and tracing.

Change #[hooq] to #[hooq(anyhow)] to enable the flavor:

use anyhow::Result;
use hooq::hooq;

#[hooq(anyhow)]
fn display_name_by_mista(val: &toml::Value) -> Result<()> {
    let name = val.get("package")?.get("name")?.as_str()?;

    if name.contains("4") {
        return Err(anyhow::anyhow!(
            "name `{name}` contains '4'. Guido Mista disallow this."
        ));
    }

    println!("Mista「name: {name}」");

    Ok(())
}

#[hooq(anyhow)]
fn main() -> Result<()> {
    let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());

    let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;

    display_name_by_mista(&cargo_toml)?;

    Ok(())
}

Output matches the manual version.

See Flavors for all built‑in presets.

Define Custom Flavors

You can define flavors in a hooq.toml placed at the crate root (CARGO_MANIFEST_DIR). Example:

[my_flavor]
method = """.inspect_err(|_| {
    eprintln!("Error @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})
.inspect(|_| {
    println!("Success @ Line {}: Col: {}\n{}", $line, $col, ::hooq::summary!($source));
})"""
hook_targets = ["?", "return", "tail_expr"]
tail_expr_idents = ["Ok", "Err"]
result_types = ["Result"]
hook_in_macros = true

[my_flavor.ok_or_else]
method = """
.ok_or_else(|| {
    format!("[Line: {}, {}]\n{}",
        $line,
        $nth,
        ::hooq::summary!($source),
    )
})
.$so_far"""

Meaning of the keys:

KeyEffect
methodMethod to hook
hook_targetsWhich of ?, return, tail_expr to hook
tail_expr_identsIdents (e.g. Err) that force hooking on tail / return
result_typesReturn type idents (e.g. Result) whose tail/return values are considered for hooks

my_flavor.ok_or_else is a sub‑flavor, inheriting settings and overriding a subset.

For all available fields and precedence rules see Attributes and Flavors.

Use the flavor in code:

use hooq::hooq;

#[hooq(my_flavor)]
fn display_name_by_mista(val: &toml::Value) -> Result<(), String> {
    // Method can be overridden by the one in flavor!
    #[hooq::method = my_flavor::ok_or_else]
    let name = val.get("package")?.get("name")?.as_str()?;

    if name.contains("4") {
        return Err(format!(
            "name `{name}` contains '4'. Guido Mista disallow this."
        ));
    }

    println!("Mista「name: {name}」");

    Ok(())
}

#[hooq(my_flavor)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let path = std::env::args().nth(1).unwrap_or("Cargo.toml".to_string());

    let cargo_toml: toml::Value = toml::from_str(&std::fs::read_to_string(path)?)?;

    display_name_by_mista(&cargo_toml)?;

    Ok(())
}

#[hooq::method = flavor_name] performs partial application of a flavor’s settings. Other fields also support this via #[hooq::field = flavor_name]. See Attributes (partial application).

Subtle note: $so_far represents the chain after initial flavor application; you cannot reference $so_far inside the base flavor method itself. Thus #[hooq(my_flavor::ok_or_else)] cannot include .$so_far, while #[hooq::method = my_flavor::ok_or_else] can.