Why did my program crash?
I wanted a simple answer and would love something similar to a stack trace. I tried some popular crates and found that they are either too complex or too much work to use. If the error is application-ending, I just want to pass it up the stack and eventually either print or serialize it. And that's exactly what this library does, while remaining as small as possible.
It implements a single type [ErrorMessage] and a single trait [WithContext], which is implemented for [Result] and [Option]. It provides two functions:
- with_err_context which takes anything that can be converted to [&str]
- with_dyn_err_context which instead takes a closure that is only run in the error case
No macros and no learning curve.
Wherever you use Rust's [?] operator, you now add .with_err_context("Short description of what you are currently doing")?
.
The output is neatly formatted, almost like a stacktrace.
A simple example
# use File;
# use io;
# use ErrorKind;
# ;
use *;
// OR just use WithContext
use WithContext;
prints
Failed to start the program
caused by: Failed to load configuration
caused by: Failed to read file 'config.json'
caused by: Kind(UnexpectedEof)
Using ErrorMessages
You can use the functions with_err_context and with_dyn_err_context with Results and Options.
with_err_context
If your error messages are static strings, you can just include them like this:
# use io;
use WithContext;
# let e0 =
produce_err
.with_err_context;
# let e1 =
produce_none
.with_err_context;
#
# assert_eq!;
# assert_eq!;
prints
Something went wrong in function 'produce_err'
caused by: Kind(UnexpectedEof)
and
Something went wrong in function 'produce_none'
with_dyn_err_context
# use io;
use *;
let variable = "Test";
# let e0 =
produce_err
.with_dyn_err_context;
# let e1 =
produce_none
.with_dyn_err_context;
#
# assert_eq!;
# assert_eq!;
prints
Something went wrong in function 'produce_err'. Extra info: Test
caused by: Kind(UnexpectedEof)
and
Something went wrong in function 'produce_none'. Extra info: Test
Creating an error message from scratch
To get an [ErrorMessage] without an underlying Error
use ErrorMessage;
new;
// prints "Error description" without listing a cause
Most of the time, you need a [Result<T, ErrorMessage>] instead.
[ErrorMessage::err] does exactly that, so you can immediately throw it with ?
:
# use ErrorMessage;
If you want to manually wrap an Error, there is the function [ErrorMessage::with_context]. Example:
# use io;
# use ErrorMessage;
with_context;
A real-world example
# use ;
# use OsStr;
# use ;
# use ;
#
#
prints
Error: Failed to get outputs
caused by: Failed to run command 'swaynsg' with args ["-t", "get_outputs"]
caused by: Failed to spawn process
caused by: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Much nicer to understand what the program was doing :)
If you had just used the ?
operator everywhere, the error would have just said:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
Features
Feature | Enabled by default | Dependencies | Effect |
---|---|---|---|
default | true | feature: "pretty_debug_errors" | Enable pretty debug errors |
pretty_debug_errors | true | Enable pretty debug errors | |
boolean_errors | false | Allow turning booleans into ErrorMessages | |
serde | false | dependency: "serde" | Allow serialization of ErrorMessages |
Feature: pretty_debug_errors
This feature is enabled by default and is responsible for the pretty error messages in the rest of this page. If the feature is disabled, the more ideomatic rust debug error formatting is used:
Error: ErrorMessage { message: "Failed to get outputs", cause: ErrorMessage { message: "Failed to run command 'swaynsg' with args [\"-t\", \"get_outputs\"]", cause: ErrorMessage { message: "Failed to spawn process", cause: Some(Os { code: 2, kind: NotFound, message: "No such file or directory" }) } } }
Formatted by hand, it looks like this:
Error: ErrorMessage {
message: "Failed to get outputs",
cause: ErrorMessage {
message: "Failed to run command 'swaynsg' with args [\"-t\", \"get_outputs\"]",
cause: ErrorMessage {
message: "Failed to spawn process",
cause: Some(
Os {
code: 2,
kind: NotFound,
message: "No such file or directory"
}
)
}
}
}
Still readable, but not as nice for humans.
Feature: boolean_errors
(disabled by default)
This adds the trait [BooleanErrors] and with it 4 functions to the [bool] type:
This allows for cool code like this:
# use Path;
use ;
#
#
or with more dynamic context:
# use Path;
use *;
#
#
Very useful, when doing lots of checks that aren't immediately errors.
Feature: serde
(disabled by default)
This feature enables serialization of [ErrorMessage]s with serde.
# use std::convert::Infallible;
# use std::io;
# use errors_with_context::prelude::*;
#
let result: Result<Infallible, _> = Err(io::Error::from(io::ErrorKind::NotFound))
.with_err_context("Failed to read file")
.with_err_context("Failed to load configuration")
.with_err_context("Failed to start the program");
let message = result.unwrap_err();
let json = serde_json::to_string_pretty(&message).unwrap();
#
# assert_eq!(json, r#"{
# "message": "Failed to start the program",
# "cause": {
# "message": "Failed to load configuration",
# "cause": {
# "message": "Failed to read file",
# "cause": {
# "message": "entity not found",
# "cause": null
# }
# }
# }
# }"#)
results in