A simple, universal error type for Rust
Rust has a great system for handling conditional success in the form of Result<T, E>, but using this in practice often leads to reinventing the wheel, especially when the program's needs are intially unknown.
I wrote this crate for myself to be my universal error type after I noticed these trends in my programs:
-
Most simple applications just need a text error for the user. That's it.
-
When building an API, dealing with FFI, or just programmatically evaluate errors, it can be necessary to have an error "kind" to disambiguate sub-errors.
-
It can sometimes be helpful to evaluate the chain of errors for determining the root cause.
The typical crates to solve these problems are anyhow and thiserror. These are great crates, but I wanted better integration between the universal error and the kind (essentially an anyhow that is "kind-aware"). This crate is more like anyhow than thiserror, as the latter could be seen as complementary and used to create the inner "kind".
cargo add uni_error- Simple/ergonomic API
- Can wrap any type that implements Display or Error
- Provides error cause chain with metadata and downcasting
- Both type safe and dynamic (string/integer) error kind
- Dereferences to stdlib Error trait
- Implements Clone
- Optional Backtrace capture
- Optional: Auto convert into http::StatusCode, axum::response::Response, or tonic::Status for usage in an API
- No required dependencies
- No macros
- No
unsafe(forbidden) - Optionally
no_std(only backtrace capability lost)
The basics:
use uni_error::*;
fn do_something() -> SimpleResult<()> {
std::fs::read("/tmp/nonexist")?;
Ok(())
}
fn main() {
println!("{}", do_something().unwrap_err());
}Add some context:
use uni_error::*;
fn do_something() -> SimpleResult<Vec<u8>> {
std::fs::read("/tmp/nonexist")
.context("Oops... I wanted this to work!")
}
fn main() {
println!("{}", do_something().unwrap_err());
}Wrap a type that doesn't implement std::error::Error:
use uni_error::*;
fn do_something() -> Result<(), String> {
// Try to do something very important here...
Err("Doh! It didn't work".to_string())
}
fn try_do_something() -> SimpleResult<()> {
do_something().context_disp("Oops... I wanted this to work!")
}
fn main() {
println!("{}", try_do_something().unwrap_err());
}Or use your own kind:
use std::borrow::Cow;
use uni_error::*;
#[derive(Debug, Default)]
enum MyKind {
#[default]
SomethingBad,
SomethingWorse(&'static str),
}
impl UniKind for MyKind {
fn context(&self, cause: Option<Cause<'_>>) -> Option<Cow<'static, str>> {
match self {
MyKind::SomethingBad => None,
MyKind::SomethingWorse(msg) => Some(Cow::Borrowed(msg))
}
}
}
fn do_something() -> UniResult<Vec<u8>, MyKind> {
std::fs::read("/tmp/nonexist")
.kind(MyKind::SomethingWorse("That was REALLY bad!"))
}
fn main() {
println!("{}", do_something().unwrap_err());
}If the error type implements std::error::Error, or is UniError<T> --> UniError<T>:
result?
UniError<T> --> UniError<U> (U must implement Default):
result.wrap()?
Option<V>:
option.wrap()?
Fallback for std::fmt::Display types:
result.wrap_disp()?
UniError<T>: if kind is equal, errors are equal
SimpleError: if context is equal, errors are equal
This is currently experimental, however, I am using this as my primary error type in all my work and a startup, so it will become production shortly.
Contributions are welcome as long they align with my vision for this crate.