#[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:
| Meta | Literal Kind | Description |
|---|---|---|
$path | string | Relative path from crate root to the file |
$line | integer | Line where the hook is inserted |
$col | integer | Column where the hook is inserted |
$source | tokens of target expression | The 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.