errs 1.0.0

A library for handling errors with reasons
Documentation

errs crates.io doc.rs CI Status MIT License

A library for handling errors with reasons for Rust

Overview

errs is an error handling library for Rust designed to focus on the "Reason" behind an error.

Expressing "Why It Failed" via the Type System

Rather than treating errors as simple message strings or type-erased objects, it embraces a design that expresses "why it failed" through types, allowing for safe and clear propagation and determination.

For error reasons, you can use anything from lightweight types like String to type-safe definitions using enum or struct, all handled flexibly with the same API. By using an enum in particular, you can not only express failure factors within the type system but also hold contextual information in its fields, propagating the situation and relevant data at the time of the error as-is. Furthermore, since reasons can be determined in a type-safe manner using reason::<T>() or match_reason, you can avoid fragile error handling that relies on string comparisons.

Decentralized Error Definition and Traceability

errs encourages defining error reasons close to where they occur. This eliminates the need to share a massive, monolithic error type across the entire application, enabling a highly maintainable design while keeping dependencies between modules clean. Type information is utilized to identify the reason, and the type identifiers required for this determination are resolved statically at compile time. This provides type-safe error handling with minimal runtime overhead.

The core Err type of the library implements std::error::Error, allowing it to integrate naturally with standard Rust error handling, including the ? operator. It can also retain lower-layer errors as causes (source errors), enabling you to manage the "Reason" of the upper layer and the "Cause" of the lower layer separately. Additionally, it automatically records the file name and line number when an error is generated, making log output and failure analysis effortless.

Powerful Error-Instantiation Notification & Monitoring Ecosystem

Furthermore, errs features a mechanism to notify error generation events. By enabling the notify or notify-tokio feature, an automatic notification can be sent to registered handlers the exact moment an Err is created. It supports synchronous handlers, generic asynchronous handlers, and asynchronous handlers tailored for Tokio. It accommodates both dynamic registration within functions and static global registration via macros. This makes it easy to implement logging, monitoring, metrics collection, and integration with telemetry systems.

While anyhow-like libraries place importance on "propagating errors flexibly," and thiserror-like libraries focus on "making error type definitions easy", errs emphasizes "expressing failure reasons through types and observing their occurrence". This library is ideal when you want to clearly manage the semantics of errors occurring within an application while integrating seamlessly with monitoring and operations infrastructure.

Install

In Cargo.toml, write this crate as a dependency:

[dependencies]
errs = "1.0.0"

If you want to use error notification, specify the notify or notify-tokio in the dependency features. The notify feature is for general use, while the notify-tokio feature is for use with the Tokio runtime.

[dependencies]
errs = { version = "1.0.0", features = ["notify"] }

If you are using Tokio, you should specify notify-tokio:

[dependencies]
errs = { version = "1.0.0", features = ["notify-tokio"] }

Usage

Locally Defined Reasons and Instantiate an Err with Them

An Err struct can be instantiated with any arbitrary error reason. Typically, a variant of an enum defined to indicate the cause or context of the error is used as the reason. This variant does not need to belong to a single, centrally managed enum; rather, it is preferable to define it close to where the error using it as a reason actually occurs.

use errs::Err;

#[derive(Debug)]
enum Reasons {
    IllegalState { state: String },
    // ...
}

let err = Err::new(Reasons::IllegalState { state: "bad state".to_string() });

An Err can also be instantiated using Err::with_source, which accepts the underlying cause error along with the reason.

use std::io::{Error, ErrorKind};
use errs::Err;

let io_error = Error::new(ErrorKind::Interrupted, "oh no!");

let err = Err::with_source(Reasons::IllegalState { state: "bad state".to_string() }, io_error);

Type-Safe Reason Identification

By using the reason::<R>(&self) method, you can extract the error reason as the specified type R. Since the return value is Result<&R, &Err>, you can use a match statement to safely branch and identify the reason in a type-safe manner.

match err.reason::<Reasons>() {
    Ok(r) => match r {
        Reasons::IllegalState { state } => println!("state = {state}"),
        _ => { /* ... */ }
    }
    Err(err) => match err.reason::<String>() {
        Ok(s) => println!("string reason = {s}"),
        Err(err) => { /* ... */ }
    }
}

Methods like match_reason allow you to write error reason identification and fallback processing elegantly using method chaining, without having to nest multiple match statements.

let val: u8 = err.match_reason::<Reasons, u8>(|r| match r {
    Reasons::IllegalState { _state } => Ok(1u8),
    _ => Ok(2u8),
}).or_match_reason::<String>(|_string| {
    Ok(3u8)
}).or_result(|_err| {
    Ok(4u8)
})?;

Function-based Error Handler Registration

To enable this feature, you must specify the feature notify or notify-tokio in Cargo.toml.

This crate optionally provides a feature to notify pre-registered error handlers when an Err is instantiated. Multiple error handlers can be registered, and you can choose to receive notifications either synchronously or asynchronously.

To register handlers inside a function (like main), you can use the following functions:

  • add_sync_err_handler: For synchronous handlers.
  • add_async_err_handler: For general-purpose asynchronous handlers.
  • add_tokio_async_err_handler: For Tokio-based asynchronous handlers.

Error notifications will not occur until the fix_err_handlers function is called. This function locks the current set of error handlers, preventing further additions and enabling notification processing.

// In your main function or initialization code:

#[cfg(feature = "notify")]
errs::add_sync_err_handler(|err, tm| {
    println!("[Sync] {}:{}:{} - {}", tm, err.file(), err.line(), err);
});

#[cfg(feature = "notify")]
errs::add_async_err_handler(|err, tm| {
    println!("[Async] {}:{}:{} - {}", tm, err.file(), err.line(), err);
});

#[cfg(feature = "notify-tokio")]
errs::add_tokio_async_err_handler(async |err, tm| {
    println!("[Tokio Async] {}:{}:{} - {}", tm, err.file(), err.line(), err);
});

// Fix the handlers to start receiving notifications.
#[cfg(any(feature = "notify", feature = "notify-tokio"))]
errs::fix_err_handlers();

Macro-based Error Handler Registration

To enable this feature, you must specify the feature notify or notify-tokio in Cargo.toml.

Alternatively, you can register handlers from a static context (outside a function body) using macros. These are useful for setting up global handlers that are compiled into your program.

  • add_sync_err_handler!: Statically registers a synchronous handler.
  • add_async_err_handler!: Statically registers a general-purpose asynchronous handler.
  • add_tokio_async_err_handler!: Statically registers a Tokio-based asynchronous handler.

These macros require function pointers, not closures.

#[cfg(feature = "notify")]
use errs::{add_async_err_handler, add_sync_err_handler};
#[cfg(feature = "notify-tokio")]
use errs::{add_tokio_async_err_handler};
use errs::Err;
use chrono::{DateTime, Utc};
use std::sync::Arc;

// Define a static synchronous handler
fn my_sync_handler(err: &Err, tm: DateTime<Utc>) {
    println!("[Static Sync] Error at {}: {}", tm, err);
}
#[cfg(feature = "notify")]
add_sync_err_handler!(my_sync_handler);

// Define a static asynchronous handler
fn my_async_handler(err: &Err, tm: DateTime<Utc>) {
    println!("[Static Async] Error at {}: {}", tm, err);
}
#[cfg(feature = "notify")]
add_async_err_handler!(my_async_handler);

// Define a static Tokio-based asynchronous handler
#[cfg(feature = "notify-tokio")]
add_tokio_async_err_handler!(async |err: Arc<Err>, tm: DateTime<Utc>| {
    println!("[Static Tokio Async] Error at {}: {}", tm, err);
});

// Later, in your main function, you still need to fix the handlers.
// errs::fix_err_handlers();

Supported Rust versions

This crate supports Rust 1.80.1 or later.

% ./build.sh msrv
  [Meta]   cargo-msrv 0.19.3

Compatibility Check #1: Rust 1.77.2
  [FAIL]   Is incompatible

Compatibility Check #2: Rust 1.87.0
  [OK]     Is compatible

Compatibility Check #3: Rust 1.82.0
  [OK]     Is compatible

Compatibility Check #4: Rust 1.79.0
  [FAIL]   Is incompatible

Compatibility Check #5: Rust 1.80.1
  [OK]     Is compatible

Result:
   Considered (min … max):   Rust 1.56.1 … Rust 1.97.1
   Search method:            bisect
   MSRV:                     1.80.1
   Target:                   x86_64-apple-darwin

License

Copyright (C) 2025-2026 Takayuki Sato

This program is free software under MIT License. See the file LICENSE in this distribution for more details.