Post: Error Handling Survey

Hey all, I wrote a new post today: Error Handling Survey. I looked at 9 of Rust's error handling crates, and wrote about them in order to figure out what they provide and what they have in common.

As error handling seems to currently be a topic of active interest,creating an overview of what's available in the ecosystem seemed like it'd be useful to have. Hope it's good!

https://blog.yoshuawuyts.com/error-handling-survey/

15 Likes

Thanks for writing this, it's great to have this all in one place!

1 Like

The similarities between throw , throws , and try could plausibly help lower the barrier for learning Rust as they have much in common with mainstream programming languages such as JavaScript, Swift, and Java. But ideally further research would be conducted to help corroborate this.

3 Likes

@yoshuawuyts

auto-enums unfortunately doesn't allow anonymous impl Error yet inside Result,

auto_enums works on Result<T, impl Error>, so you can write as follows:

use auto_enums::auto_enum;
use std::{error::Error, fs};

#[auto_enum(Error)]
fn main() -> Result<(), impl Error> {
    let num = i8::from_str_radix("A", 16)?;
    let file = fs::read_to_string("./README.md")?;
    Ok(())
}

...but I think it is difficult to support it efficiently with proc-macro:

Another problem is that the current ? operator support generates the same number of variants as the ? operators are used. This may generate code that is less efficient than boxing based error handling for functions that use many ? operators. Since proc_macro doesn't understand types (i.e., it is difficult to optimize that), proc-macro crate, which provides only this feature, it will not be very useful.

(Or can the compiler optimize this?)

1 Like

Thanks for the writeup! This must have taken you quite a while to investigate and test all the options. I'm sure that people will appreciate the hard work!


I've some small corrections and clarifications relating to SNAFU:

It seems to draw from experiences of using failure

I've never used failure myself, due to ideological differences. One of the main ones is that I believe that error types should be able to be compared for equality. I really like testing my error cases via unit tests.

and ships a comparison [to failure]

The comparison in the guide is mostly due to the popularity of failure. If I received similar questions about migrating from other libraries, I'd add guide sections for those too.

Snafu also don't thinks [sic] having shorthands to create errors from strings is very important

In general, yes, I believe that stringly-typed errors are an anti-pattern as it's basically impossible to perform any kind of code inspection of the error. I also have some grand ideas for how to support i18n of the error messages themselves.

stating: "It's unclear what benefit Failure provides here."

That's as compared to just returning a Box<dyn Error>, which has built-in support for converting from strings.

let config = fs::read(filename).context(Error::OpenConfig { filename })?;
ensure!(id == 42, Error::UserIdInvalid { user_id });

I'm not sure that this will compile — one of the most contentious design decisions about SNAFU is that it generates types called context selectors. These match the naming of the variants, but are distinct types. I believe this example should be

let config = fs::read(filename).context(OpenConfig { filename })?;
ensure!(id == 42, UserIdInvalid { user_id });

The example also takes Path instead of a &Path or the flexible impl AsRef<Path> — this is likely to cause a compiler error.

Snafu provides the following features:

I'd add it also has

  • support for futures 0.1 and 0.3 Future and Stream.
  • support for no-std usecases

Support for backtraces

The application has to opt in to decide how this is implemented. By default, backtraces are inert — a library can add them freely without requiring any user of that library to also compile an additional crate or rely on nightly. The application can then enable a feature flag that determines what snafu::Backtrace means:

  1. Internal implementation (currently using the backtrace crate)
  2. Public implementation using the backtrace crate
  3. Public implementation using the unstable std::backtrace::Backtrace

SNAFU tries to stretch really hard to be usable for library implementors and application writers, as well as supporting a wide range of versions (currently back to Rust 1.31 due to the syn/quote dependencies).

But it misses the "print from main"

We've got related issues that would help ease some of that annoyance, mostly waiting on someone to have time to contribute.

snafu all seem to make use of format-string like interpolation

SNAFU directly passes all of the arguments to write!, to allow for the full flexibility of the existing formatting infrastructure. This was one of my annoyances with other tools that tried to simplify that for me. The ability to call Path::display is a big benefit.

9 Likes