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

#[hooq] to Capture Error Lines

We continue with the example from Introduction to explain what the hooq macro does to source code and how to use it.

use std::error::Error;

use hooq::hooq;

#[hooq]
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
    // Loading APP_HOST
    let host = std::env::var("APP_HOST")?;

    // Loading APP_PORT
    let port = std::env::var("APP_PORT")?;

    // Convert to u16
    let port: u16 = port.parse()?;

    Ok(format!("{host}:{port}"))
}

#[hooq]
fn main() -> Result<(), Box<dyn Error>> {
    let host_and_port = load_host_and_port()?;

    println!("Server is running on: {}", host_and_port);

    // snip

    Ok(())
}

#[test]
fn test_load_host_and_port() {
    unsafe {
        std::env::set_var("APP_HOST", "localhost");
        std::env::set_var("APP_PORT", "8080");
    }
    let host_and_port = load_host_and_port().unwrap();
    assert_eq!(host_and_port, "localhost:8080");
}

Adding the #[hooq] attribute macro to a function inserts the following default hooq method inspect_err in front of each ?, and after the value of each return and tail expression (only when the function’s return type matches a hook target such as Result).

.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}");
})

Items like $path are hooq meta variables. See Meta Variables for full details; those used in the default method are:

MetaLiteral KindDescription
$pathstringRelative path from crate root to the file
$lineintegerLine where the hook is inserted
$colintegerColumn where the hook is inserted
$sourcetokens of target expressionThe original expression being hooked (for logging)

The function load_host_and_port expands roughly like this (formatting may differ). Using cargo expand will show similar output:

---
source: mdbook-source-code/index/tests/test.rs
expression: "format!(\"{stdout}\")"
---
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
use std::error::Error;
use hooq::hooq;
fn load_host_and_port() -> Result<String, Box<dyn Error>> {
    let host = std::env::var("APP_HOST")
        .inspect_err(|e| {
            let path = "mdbook-source-code/index/src/main.rs";
            let line = 8usize;
            let col = 41usize;
            let expr = "   8>    std::env::var(\"APP_HOST\")?\n    |";
            ::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
        })?;
    let port = std::env::var("APP_PORT")
        .inspect_err(|e| {
            let path = "mdbook-source-code/index/src/main.rs";
            let line = 11usize;
            let col = 41usize;
            let expr = "  11>    std::env::var(\"APP_PORT\")?\n    |";
            ::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
        })?;
    let port: u16 = port
        .parse()
        .inspect_err(|e| {
            let path = "mdbook-source-code/index/src/main.rs";
            let line = 14usize;
            let col = 33usize;
            let expr = "  14>    port.parse()?\n    |";
            ::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
        })?;
    Ok(format!("{0}:{1}", host, port))
}
fn main() -> Result<(), Box<dyn Error>> {
    let host_and_port = load_host_and_port()
        .inspect_err(|e| {
            let path = "mdbook-source-code/index/src/main.rs";
            let line = 21usize;
            let col = 45usize;
            let expr = "  21>    load_host_and_port()?\n    |";
            ::std::eprintln!("[{0}:{1}:{2}] {3:?}\n{4}\n", path, line, col, e, expr);
        })?;
    {
        ::std::io::_print(format_args!("Server is running on: {0}\n", host_and_port));
    };
    Ok(())
}

Warning: line!() macro is discouraged here.

You might think: “Why not just use line!()? Adding a $line meta variable increases cognitive load.”

This is intentional: line!() does not point to the line of the hooked expression; it points to the attribute line (#[hooq] or #[hooq::method(...)]).

We want the exact line of the ? or other target, hence $line exists.

Thanks to this expansion, running the program as shown in Introduction yields output identifying the precise lines where errors occurred:

$ APP_PORT=10 cargo run -q
[mdbook-source-code/index/src/main.rs:8:41] NotPresent
   8>    std::env::var("APP_HOST")?
    |
[mdbook-source-code/index/src/main.rs:21:45] NotPresent
  21>    load_host_and_port()?
    |
Error: NotPresent

The default method is helpful, but you will likely want to customize it. We cover customization in the next lesson.