[−][src]Crate status
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
bail | Return early with an error. |
ensure | Return early with an error if a condition is not satisfied. |
Structs
AdhocContext | Adhoc |
Chain | Iterator of a chain of source errors. |
InternalStatus | View of |
NoContext | No context needed. |
Status | A container for use in |
TerminatingStatus | For use with |
Unkind | Adhoc |
Traits
AdhocValue | Trait alias for values in a |
Context | Adds nuance to errors. |
Kind | Trait alias for types that programmatically specify the status. |
ResultStatusExt | Modify the |