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 *;
// 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 WithContext;
produce_err
.with_err_context;
produce_none
.with_err_context;
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 *;
let variable = "Test";
produce_err
.with_dyn_err_context;
produce_none
.with_dyn_err_context;
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 ?
:
If you want to manually wrap an Error, there is the function ErrorMessage::with_context
.
Example:
;
with_context
A real-world example
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 ;
let path = new;
path.exists
.error_if_false?;
or with more dynamic context:
use *;
let path = new;
path.exists
.error_dyn_if_false?;
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.
let result: = Err
.with_err_context
.with_err_context
.with_err_context;
let message = result.unwrap_err;
let json = to_string_pretty.unwrap;
results in