errortools 0.1.0

Quality of life utilities for error handling in Rust.
Documentation
  • Coverage
  • 100%
    17 out of 17 items documented1 out of 10 items with examples
  • Size
  • Source code size: 32.42 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.09 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 18s Average build duration of successful builds.
  • all releases: 18s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • Homepage
  • maxwase/errortools
    2 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • maxwase

errortools

crates doc

Tired of writing this in every project?

fn main() {
    if let Err(e) = run() {
        eprintln!("error: {e}");
        std::process::exit(1);
    }
}

fn run() -> Result<(), MyError> { todo!() }

Because returning Result from main uses Debug, which gives you this:

Error: Outer(Inner(Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })))

We have a solution: MainResult.

Example

use errortools::MainResult;
use std::{fs, io};

#[derive(Debug, thiserror::Error)]
enum Error {
    #[error("failed to load config")]
    Config(#[source] io::Error),
}

fn main() -> MainResult<Error> {
    fs::read_to_string("missing.toml").map_err(Error::Config)?;
    Ok(())
}

Output:

Error: failed to load config: No such file or directory (os error 2)

The error and its full source chain are joined with ": " — no boilerplate, no run() wrapper, no manual loop.

Tree format

Prefer a multi-line view? Swap the format strategy:

use errortools::{MainResult, Tree};
use std::{fs, io};

#[derive(Debug, thiserror::Error)]
enum AppError {
    #[error("failed to load config")]
    Config(#[source] io::Error),
}

fn main() -> MainResult<AppError, Tree> {
    let _ = fs::read_to_string("missing.toml").map_err(AppError::Config)?;
    Ok(())
}
Error: failed to load config
└── No such file or directory (os error 2)

Logging in place

Sometimes you cannot return and need to log the full source chain right where the error happens. The FormatError extension trait works on any error:

use errortools::FormatError;

if let Err(e) = do_thing() {
    tracing::error!("do_thing failed: {}", e.one_line());
    // do_thing failed: outer: middle: inner
}

For ad-hoc strategies, pick the format inline with formatted::<F>():

use errortools::{FormatError, Tree};

if let Err(e) = do_thing() {
    eprintln!("{}", e.formatted::<Tree>());
    // outer
    // └── middle
    //     └── inner
}

Custom formats

Implement the Format trait on a unit type:

use core::{error::Error, fmt};
use errortools::{Format, FormatError, chain};
use itertools::Itertools;

struct Arrow;
impl Format for Arrow {
    fn fmt(error: &dyn Error, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", chain(error).format(" -> "))
    }
}

println!("{}", my_error.formatted::<Arrow>()); // outer -> middle -> inner

How it works

MainResult<E, F> is a type alias:

use errortools::{DisplaySwapDebug, Formatted, OneLine};

pub type MainResult<E, F = OneLine> = Result<(), DisplaySwapDebug<Formatted<E, F>>>;

DisplaySwapDebug swaps the Debug and Display impls of its inner type, so when main prints the error via Debug, you actually get its Display output — formatted by the chosen strategy. ? converts your error automatically via the blanket From impl.

Examples

Runnable examples in examples/:

Example What it shows
one_line MainResult with default OneLine format
tree MainResult<E, Tree> for indented multi-line output
format_error FormatError trait for ad-hoc formatting
custom_format A custom Format strategy
transparent #[error(transparent)] pass-through with #[from]

Run with: cargo run --example <name>.

Features

Feature Default Effect
std yes Enables itertools/use_std. Disable for no_std.