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

Introduction

hooq is an attribute macro that can (when desired) insert methods in front of the ? operator1, return, and tail expressions.

use hooq::hooq;

#[hooq]
#[hooq::method(.map(|v| v * 2))]
fn double(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
    let res = s.parse::<u32>()?;
    Ok(res)
}

fn double_expanded(s: &str) -> Result<u32, Box<dyn std::error::Error>> {
    let res = s.parse::<u32>().map(|v| v * 2)?;
    Ok(res)
}

#[test]
fn test() {
    assert_eq!(double("21").unwrap(), double_expanded("21").unwrap());
}

fn main() {
    println!("double_hooked: {}", double("21").unwrap());
    println!("double: {}", double_expanded("21").unwrap());
}

It ships with rich presets so you can easily hook error logging and similar behavior onto Result values.

Why use hooq?

We will motivate hooq using the following source code containing a function that returns a Result type:

use std::error::Error;

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

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

If you run it with all required environment variables set, it works without errors:

$ APP_HOST=127.0.0.1 APP_PORT=10 cargo run -q
Server is running on: 127.0.0.1:10

Now run it in a way that triggers an error:

$ APP_PORT=10 cargo run -q
Error: NotPresent

The contents of the Box<dyn Error> returned from main are printed; we (probably) have a missing environment variable.

But this error output is terrible:

  • You cannot tell the context or what kind of error it is
  • You cannot tell where the error occurred

Most likely the author of this Rust program did not want to build a formal application with fine‑grained error handling, but just a small casual CLI tool. Rust’s error output, however, can be rather unforgiving if you cut corners2.

Enter the hooq attribute macro!

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

Simply adding #[hooq] magically produces a pseudo stack trace for errors:

$ 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

Apparently the APP_HOST environment variable was missing. We can also see how the error propagated (line 8, then line 21, etc.).

What exactly did the hooq macro do? That will be explained later.


  1. The former try macro.

  2. “Couldn’t we just use unwrap at this scale?” I hate perceptive brats like you… Even so, not being able to use Result comfortably would be inconvenient, so the hooq macro remains meaningful.