error2 0.13.1

A simple error handle library for Rust
Documentation

Comprehensive error handling library with detailed backtrace tracking.

error2 provides enhanced error handling capabilities for Rust applications, focusing on detailed error propagation tracking and ergonomic error conversion.

Features

  • 🔍 Backtrace Tracking - Automatically capture error creation location; manually record propagation with .attach()
  • 🔗 Error Chaining - Chain errors from different libraries while preserving context
  • 🎯 Derive Macro - #[derive(Error2)] for easy error type creation
  • 🔄 Type Conversion - Result<T, E1> -> Result<T, E2>, Option<T> -> Result<T, E> with .context()
  • 📦 Type Erasure - BoxedError2 for anyhow-like ergonomics

Quick Start

Add to your Cargo.toml:

[dependencies]
error2 = "0.13.1"

Define your error types:

use std::io;

use error2::prelude::*;

#[derive(Debug, Error2)]
pub enum MyError {
    #[error2(display("IO error: {source}"))]
    Io {
        source: io::Error,
        backtrace: Backtrace,
    },

    #[error2(display("not found: {key}"))]
    NotFound { key: String, backtrace: Backtrace },
}

Use in your functions:

# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# pub enum MyError {
#     #[error2(display("IO error"))]
#     Io { source: io::Error, backtrace: Backtrace },
#     #[error2(display("not found: {key}"))]
#     NotFound { key: String, backtrace: Backtrace },
# }
fn read_config(path: &str) -> Result<String, MyError> {
    // Convert io::Error to MyError::Io
    let content = std::fs::read_to_string(path).context(Io2)?;

    // Convert Option to Result
    let value = content
        .lines()
        .next()
        .context(NotFound2 { key: "first line" })?;

    Ok(value.to_string())
}

Three Error Patterns

Error2 supports three types of errors based on their field structure:

1. Root Error (New Error Origin)

Use when creating a new error (not wrapping another):

# use error2::prelude::*;
#[derive(Debug, Error2)]
pub enum AppError {
    #[error2(display("invalid ID: {id}"))]
    InvalidId {
        id: i64,
        backtrace: Backtrace, // Only backtrace, no source
    },
}

2. Std Error (Wrapping std::error::Error)

Use when wrapping standard library or third-party errors:

# use error2::prelude::*;
# use std::io;
#[derive(Debug, Error2)]
pub enum AppError {
    #[error2(display("file error"))]
    FileError {
        source: io::Error,    // Wrapped error
        backtrace: Backtrace, // New backtrace
    },
}

3. Error2 Error (Chaining Error2 Types)

Use when wrapping another Error2 type (reuses backtrace):

# use error2::prelude::*;
# #[derive(Debug, Error2)]
# #[error2(display("config error"))]
# pub struct ConfigError { backtrace: Backtrace }
#[derive(Debug, Error2)]
pub enum AppError {
    #[error2(display("configuration failed"))]
    Config {
        source: ConfigError, // Only source, backtrace reused
    },
}

Core Traits

  • [Error2] - Extends std::error::Error with backtrace support
  • [Context] - Type conversion: Result<T, Source> -> Result<T, Target>, Option<T> -> Result<T, E>
  • [Attach] - Record error propagation locations
  • [RootError] - Convenience methods for creating root errors

Type Erasure

[BoxedError2] provides anyhow-like ergonomics:

use error2::prelude::*;

fn do_something() -> Result<(), BoxedError2> {
    std::fs::read_to_string("file.txt").context(ViaStd)?; // Convert to BoxedError2
    Ok(())
}

Location Tracking

Use .attach() to record error propagation:

# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# #[error2(display("error"))]
# struct MyError { source: io::Error, backtrace: Backtrace }
# fn inner() -> Result<(), MyError> { Ok(()) }
fn outer() -> Result<(), MyError> {
    let result = inner().attach()?; // Records this location
    Ok(result)
}

The backtrace shows multiple locations:

# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# #[error2(display("error"))]
# struct MyError { source: io::Error, backtrace: Backtrace }
# fn inner() -> Result<(), MyError> {
#     let err = io::Error::new(io::ErrorKind::NotFound, "not found");
#     Err(err).context(MyError2)
# }
# fn outer() -> Result<(), MyError> { inner().attach() }
# fn main() {
use regex::Regex;

if let Err(e) = outer() {
    let msg = e.backtrace().error_message();

    // Full error format with multiple locations:
    // MyError: error
    //     at /path/to/file.rs:496:14
    //     at /path/to/file.rs:498:45
    // std::io::error::Error: not found

    let re = Regex::new(concat!(
        r"(?s)^.+MyError: error",
        r"\n    at .+\.rs:\d+:\d+",
        r"\n    at .+\.rs:\d+:\d+",
        r"\nstd::io::error::Error: not found$",
    ))
    .unwrap();
    assert!(re.is_match(msg.as_ref()));
}
# }