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
Kindof status and the metadata, orContext, of the status. - Dealing with error-sites not knowing enough to describe the error but allowing the
Contextto 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
Kinds.
#[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, enums were used when maturing the code but error codes still have a
place.
enums:
- 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
Statusinline for error handling.