Interfaces for working with Errors.
Error Handling With Subspace
The Subspace C++ library provides two complementary systems for
constructing/representing, reporting, propagating, and reacting to errors.
These responsibilities are collectively known as “error handling.” The
components of the first system, the panic handling interfaces, are most
commonly used to represent bugs that have been detected in your program. The
components of the second system,
Result
, the Error
concept,
and user defined types, are used to represent anticipated runtime failure
modes of your program.
Unlike exceptions, the error handling facilities here are explicit at each layer of a call stack, with a convenient way to generically propagate errors of any type without templates.
The Panic Interfaces
The following are the primary interfaces of the panic system and the responsibilities they cover:
sus_panic
(Constructing, Propagating)SUS_PROVIDE_PRINT_PANIC_LOCATION_HANDLER
(Reporting)SUS_PROVIDE_PANIC_HANDLER
(Reacting)
The following are the primary interfaces of the error system and the responsibilities they cover:
Result
(Propagating, Reacting)- The
Error
concept (Reporting) - The
DynError
type (Reporting) - User defined types (Constructing / Representing)
- TODO:
downcast
(Reacting) - TODO: The
TRY
macro (Propagating) - The
Try
concept (Propagating, Constructing)
Converting Errors into Panics
The panic and error systems are not entirely distinct. Often times errors
that are anticipated runtime failures in an API might instead represent bugs
to a caller. For these situations the Subspace C++ library provides APIs for
constructing panics with an Error
as it's source.
These functions are equivalent, they either return the inner value if the
Result
is Ok
or panic if the Result
is Err
printing the inner error
as the source. The only difference between them is that with expect
you
provide a panic error message to be printed alongside the source, whereas
unwrap has a default message indicating only that you unwrapped an Err
.
Of the two, expect
is generally preferred since its msg
field allows you
to convey your intent and assumptions which makes tracking down the source
of a panic easier. unwrap
on the other hand can still be a good fit in
situations where you can trivially show that a piece of code will never
panic, such as u32::try_from(404).unwrap()
or early prototyping.
Common Message Styles
There are two common styles for how people word expect messages. Using the message to present information to users encountering a panic (“expect as error message”) or using the message to present information to developers debugging the panic (“expect as precondition”).
In the former case the expect message is used to describe the error that has occurred which is considered a bug. Consider the following example:
// Read environment variable, panic if it is not present
auto path = sus::env::var("IMPORTANT_PATH").unwrap();
In the “expect as error message” style we would use expect to describe that the environment variable was not set when it should have been:
auto path = sus::env::var("IMPORTANT_PATH")
.expect("env variable `IMPORTANT_PATH` is not set");
In the “expect as precondition” style, we would instead describe the reason
we expect the Result should be Ok. With this style we would prefer to write:
auto path = sus::env::var("IMPORTANT_PATH")
.expect("env variable `IMPORTANT_PATH` should be set by "
"`wrapper_script.sh`");
The “expect as error message” style does not work as well with the default output of the std panic hooks, and often ends up repeating information that is already communicated by the source error being unwrapped:
PANIC! at 'env variable `IMPORTANT_PATH` is not set: NotFound',
sus/result/result.h:792:11
In this example we end up mentioning that an env variable is not set, followed by our source message that says the env is not present, the only additional information we’re communicating is the name of the environment variable being checked.
The “expect as precondition” style instead focuses on source code readability, making it easier to understand what must have gone wrong in situations where panics are being used to represent bugs exclusively. Also, by framing our expect in terms of what “SHOULD” have happened to prevent the source error, we end up introducing new information that is independent from our source error.
PANIC! at 'env variable `IMPORTANT_PATH` should be set by
`wrapper_script.sh`: NotPresent', sus/result/result.h:792:11
In this example we are communicating not only the name of the environment variable that should have been set, but also an explanation for why it should have been set, and we let the source error display as a clear contradiction to our expectation.
Hint: If you’re having trouble remembering how to phrase expect-as-precondition style error messages remember to focus on the word “should” as in “env variable should be set by blah” or “the given binary should be available and executable by the current user”.