1 unstable release
0.1.0 | Jan 10, 2022 |
---|
#937 in Rust patterns
39KB
440 lines
coded
This is a concrete error type with an ErrorKind
enum matching
Google's "canonical error codes". You may know these from Google Cloud
errors,
absl::Status
, or
gRPC status codes.
Status
The error code enum is exceptionally stable. The overall crate API is a work in progress. Ideas welcome!
I'll convert Moonfire NVR to this shortly.
Example
use coded::{bail, err, Error, ErrorBuilder, ErrorKind, ResultExt};
/// Reads application config from its well-known paths.
fn read_config() -> Result<MyConfig, Error> {
for path in &PATHS {
match read_json(p) {
Ok(c) => return Ok(c),
Err(e) if e.kind() == ErrorKind::NotFound => { /* keep trying */ },
// The `bail!` macro is a convenient, flexible shorthand.
Err(e) => bail!(e, msg("can't read {}", p.display()))
}
}
// `bail!` lets us write `NotFound` without `use ErrorKind::*`.
bail!(NotFound, msg("no config file at any of {:?}", PATHS))
}
/// Reads a JSON object from the given path.
///
/// This returns an `ErrorBuilder` rather than an `Error`, avoiding a redundant
/// entry in the error chain when it's wrapped by the caller.
fn read_json<T: Deserialize>(p: &Path) -> Result<(), ErrorBuilder> {
// There's automatic conversion from std::io::Error to coded::ErrorBuilder which
// selects an appropriate ErrorKind.
let raw = std::fs::read(p)?;
// ResultExt::err_kind wraps any std::error::Error impl, using the supplied
// kind. It doesn't add a message.
serde_json::from_str(&raw).err_kind(ErrorKind::InvalidArgument)
}
fn main() {
if let Err(e) = inner_main() {
// `Error::chain` prints not only `e` itself but also the full chain of sources.
eprintln!("Fatal error:\n{}", e.chain());
std::process::exit(1);
}
}
fn inner_main() -> Result<(), Error> {
let config = read_config()?;
// ...
}
When should I use it?
- When you want the advantages of this single well-designed,
general-purpose error code enum:
- familiarity: when you use the same error codes widely, the expectations for handling them are clear.
- monitoring: you can meaningfully aggregate errors returned by different APIs with these codes.
- stability: existing error codes and their numbers will never change.
The enum is marked
#[non_exhaustive]
because new codes could be added, but this hasn't happened since 2015. This is great for RPC or crate boundaries. - gRPC interoperability: many services (not only Google's) use these error codes already.
- When you want your errors to emphasize how the caller should handle them rather than details of your implementation or dependencies. See the blog post Rust Error Handling.
- When returning
Ok
has to be cheap. AResult<(), coded::Error>
is one word, so returningOk
is faster than with larger error types. - When you want rich human-readable error messages with the code, details,
complete error chain, and more. Currently "more" can be stack traces
(controlled by the application's
Cargo.toml
and environment variables). In the future, perhapstracing_error::SpanTrace
and/or arbitrary payloads. - When you want to return errors easily with the
err!
andbail!
macros.
When shouldn't I use it?
- When you don't care about error codes at all. You might be more interested
in
anyhow
,eyre
, orsnafu::Whatever
. - When you want an absolutely stable error type right now. As written above,
the actual enum values aren't changing, but it's a little early for the rest
of
coded
's API to reach 1.0. (Note: if there's demand, I could split the absolutely-stablecoded::{ErrorKind, ToErrorKind}
types into their own crate. Then you could have a stable error type by wrappingcoded::Error
in your own crate's publicError
type.) - When you need exhaustive enums with custom fields to guide the caller in handling domain-specific errors, sometimes at the cost of API stability.
- When returning
Err
has to be cheap.coded::Error
isn't cheap: it requires heap allocation, and currently stack traces can't be disabled for particular libraries or call sites. - When you want to just pass along other crates' errors with
?
without having to make your own wrapper around those error types and/orcoded
. Due to Rust's orphan rule,coded::ToErrorKind
can only be implemented where the error is defined or incoded
. This limits ergonomics. (Once specialization is stable,?
could pass along other types usingErrorKind::Unknown
, but this might be more of a footgun than a help. Likewise, we could fight the orphan rule with something likeinventory
, but we probably shouldn't.)
Error Handling in a Correctness-Critical Rust Project
describes how many of these apply to the sled
database.
If you need your own error type but hate writing boilerplate, try the derive
macros from thiserror
or
snafu
).
How should I use it?
Return coded::Error
. Use comments to document the error kinds your API
returns in certain situations. Feel free to add additional error kinds without a
semver break, as callers must match non-exhaustively.
What's missing?
- The ability to extend the status with typed payloads as
absl::Status
supports. I'd like to use support baked into thestd::error::Error
trait for this (see RFC 2895) but it doesn't exist yet.coded
might grow its own API for this in the meantime. - Support for serializing and deserializing as protobufs. There are at least
three Rust protobuf libraries (
prost
,protobuf
, andquick-protobuf
). We could support each via Cargo feature flags.
License
Apache 2.0 or MIT, at your option.
Author
Scott Lamb <slamb@slamb.org>
Dependencies
~0–5MB
~10K SLoC