The state of error handling in the 2018 edition

If you're making a library, don't use the failure crate. Instead:

  1. define an enum that covers meaningful error cases for your library,
  2. implement std::error::Error for that enum.

And that's it. This is going to be fast/cheap (no heap allocation on error), and precise and easy to use (instead of an opaque object, stringly-typed errors, or a nested Kind type, you get exact cases right there).

It won't have extra features, like backtraces or contexts, but I'm going to argue that for a library these are unnecessary, and just leak implementation details.

Thanks to the std::error::Error implementation, writers of applications will be able to seamlessly wrap/convert such errors to Failure or other types.

16 Likes

These days I tend to use std::error::Error for everything. If you want a generic error type and you don't care about what's in it (apart from being able to print an error chain) use Box<dyn std::error::Error + 'static> with Send and Sync if you need. That way you can even recover the original type if you need to.

3 Likes

If you already implemented Debug and Display does it really change something by adding impl std::error::Error for SRCError{} ?

Yes, it enables conversion to Box<dyn std::error::Error>.

A nice alternative to the derive part of failure is snafu.

The big advantage of using failure::Error is painless error propagation with backtraces, which is awesome in application code. Especially in early prototyping stages where things are in flux and writing out error enums can be annoying. (the default Debug impl prints the whole backtrace)

There are several problems with failure though:

  • it's not maintained/developed anymore
  • the Fail trait, is made redundant by improvements to std::error::Error (backtrace + downcasting support), is an annoyance.
    failure::Error does not implement std::error::Error which can be very annoying when eg passing something to a library where you need the std::Error bound
  • failure::Error can make it way to easy to forget about errors and just propagate things. You get nice backtraces but can completely lose track of what errors can occur where

As other have said, I think libraries should just stick to using a hand-written sum error enum and not push any extra error handling dependencies on the user. Maybe offer backtraces as a optional feature.

Personally I'd love to see a cleaned up failure 0.2.0 that takes care of the design issues around Fail and keeps the good parts.

3 Likes

This looks like it could be a good successor to Failure: fehler.

It has the backtrace ability like Failure and has a throw! macro, but it seems very simple.

2 Likes

Rewriting function signatures and bodies does not feel simple to me. Certainly, this is a matter of preferences, and my preference is against this.

How about this one: Anyhow. :wink:

That one is even simpler. I think I might like that a bit more.

6 Likes

Thanks, this one looks like something I'll try the next time :slight_smile:

1 Like

thiserror was recently announced. Described by @dtolnay in this way on reddit:

I believe that we want at least two, for two quite different use cases. One use case where errors are "whatever, just make it easy", and a different where every error type is artisanally designed. In my recommended ecosystem these are served by anyhow and thiserror respectively.

11 Likes

Yeah, that combo seems like a very lightweight, but meaningful way to handle errors easily in Rust without adding a bunch of extra stuff to learn and use.

I'll probably use that in my next projects. :slight_smile: