Expand description
Status
: An Error
container for Rust.
An Error
container lowers the overhead for reporting the status via Result<_, E>
.
Unlike the error-wrapping pattern found in cargo
and generalized in anyhow
, the pattern
implemented in Status
comes from projects which try to address the
following requirements:
- Programmatically respond to both the
Kind
of status and the metadata, orContext
, of the status. - Dealing with error-sites not knowing enough to describe the error but allowing the
Context
to be built gradually when unwinding where there is relevant information to add. - Localizing the rendered message.
- Allowing an application to make some phrasing native to its UX.
- Preserving all of this while passing through FFI, IPC, and RPC (TODO #1 #2).
These requirements are addressed by trading off the usability of per-site custom messages with
messages built up from common building blocks. The Kind
serves as a static description of the
error that comes from a general, fixed collection. Describing the exact problem and tailored
remediation is the responsibility of the Context
which maps general, fixed keys with
runtime-generated data.
Status
grows with your application:
§Prototyping
When prototyping, you tend to focus on your proof of concept and not worry about handling all
corner cases or providing a clean API. status
helps you with this by providing an ad-hoc
Kind
, Unkind
, and an AdhocContext
.
type Status = status::Status;
type Result<T, E = Status> = std::result::Result<T, E>;
fn read_file(path: &Path) -> Result<String> {
std::fs::read_to_string(path)
.map_err(|e| {
Status::new("Failed to read file")
.with_internal(e)
.context_with(|c| c.insert("Expected value", 5))
})
}
fn main() -> Result<(), status::TerminatingStatus> {
let content = read_file(Path::new("Cargo.toml"))?;
println!("{}", content);
Ok(())
}
§Maturing the code
After prototyping, you probably want to start handling all the corner cases and have a more
strictly defined API. You can do this by using an enum
for your Kind
. As you play
whack-a-mole in cleaning up error handling, you might want to still allow some ad-hoc
Kind
s.
#[derive(Copy, Clone, Debug, derive_more::Display, derive_more::From)]
enum ErrorKind {
#[display(fmt = "Failed to parse")]
Parse,
#[display(fmt = "{}", "_0")]
#[doc(hidden)]
__Other(&'static str),
}
type Status = status::Status<ErrorKind>;
type Result<T, E = Status> = std::result::Result<T, E>;
fn read_file(path: &Path) -> Result<String, Status> {
std::fs::read_to_string(path)
.map_err(|e| {
Status::new("Failed to read file").with_internal(e)
})
}
And once you are done, you might choose to remove ErrorKind::__Other
completely:
#[derive(Copy, Clone, Debug, derive_more::Display)]
enum ErrorKind {
#[display(fmt = "Failed to read file")]
Read,
#[display(fmt = "Failed to parse")]
Parse,
}
fn read_file(path: &Path) -> Result<String, Status> {
std::fs::read_to_string(path)
.map_err(|e| {
Status::new(ErrorKind::Read).with_internal(e)
})
}
The same progressions happens with Context
, from AdhocContext
to hand-written
Context
.
§FAQ
§Why Status
?
For an “error” crate that wanted to focus on the programmatic use-case, the typical synonyms for “error” were too strong because one man’s error is another man’s expected case. For example, you might have a case where you need to silence some “errors” and move on.
§When should my Kind
be an enum
or an error code?
In the above examples, enum
s were used when maturing the code but error codes still have a
place.
enum
s:
- Strictly typed
Error codes:
- FFI, IPC, and RPC between binaries with different iterations of the error codes.
- Localizaton: An approach to localization is lookup tables. Keeping them separate from the application allows updating them without recompiling.
- Interoperating with an ecosystem standardized on an error code system (like
HRESULT
).
When using error codes, be sure to wrap them in a newtype to avoid mixing meanings.
Macros§
Structs§
- Adhoc
Context - Adhoc
Context
. - Chain
- Iterator of a chain of source errors.
- Internal
Status - View of
Status
, exposing implementation details. - NoContext
- No context needed.
- Status
- A container for use in
Result<_, Status>
.. - Terminating
Status - For use with
main
- Unkind
- Adhoc
Kind
.
Traits§
- Adhoc
Value - Trait alias for values in a
AdhocContext
- Context
- Adds nuance to errors.
- Kind
- Trait alias for types that programmatically specify the status.
- Result
Status Ext - Modify the
Status
inline for error handling.