
Overview of Rust error handling libraries - agluszak
https://blog.yoshuawuyts.com/error-handling-survey/
======
cytzol
This is a nice overview of Rust's available error-handling crates, and some
improvements to the language that are error-related. But I'd like to quickly
point out that you do not _have_ to pick a library from this list! I don't
want anybody to read this article and go away thinking "I need to learn nine
libraries in order to use Rust effectively". In the majority of cases, I've
found what the language and std offer is enough for me.

(In fact, it leaves my favourite solution out: using the derive_more crate to
auto-generate From implementations for the ? operator to use. From is a
general trait, useful for far more than error handling, which is probably why
it's omitted here.)

~~~
weberc2
Please forgive the tangent, but I'm curious about how people are using Rust if
it's not in their primary day job? My impression is that there aren't yet many
Rust jobs, but many practitioners. What are people building with it? For Rust
enthusiasts, how do you use it? Are you contributing to an open source project
or do you have your own pet projects? In either case, what are the projects?
I'm asking because I might have some time to play around with it over break.

~~~
TheDong
Here's a list of applications written in rust: [https://github.com/rust-
unofficial/awesome-rust#applications](https://github.com/rust-
unofficial/awesome-rust#applications)

People are using rust as they do other general purpose programming languages
that have somewhat of a focus on systems programming: to write code. Your
question isn't really easy to answer in the same way that someone asking "what
are people building with python?" isn't useful. The answer is everything from
webpages to text editors to operating systems, and everything in between.

~~~
weberc2
Programming languages lend themselves to different applications and the kinds
of projects one contributes to in one's hobby time are different than those
one contributes to in one's professional time. As such, I wouldn't expect
hobbyist Rust users to work on the same distribution of projects as
professional Python developers. In any case, I just want to know what people
are inspired to work on with Rust; it's an open ended question, and it's okay
that this question has no single easy answer. (Also, "what are people building
with Python?" is a very interesting question to me; it's pretty much why
[https://github.com/trending?l=python](https://github.com/trending?l=python)
and similar exist--and note, the kinds of projects that trend for Python _are_
very different from those that trend for Rust).

------
thenewwazoo
I've never quite understood all the interest in these error-handling
libraries. I've had great success even in large projects by impl'ing From when
I need implicit conversion between error types. Most of the time, though, I
want to actually _handle_ the error at the calling site. I don't "bubble up"
errors because they _mean different things_ at different levels of the stack.

~~~
oconnor663
I think the biggest benefit of these is automatic backtraces. That's what I
tend to miss from e.g. Python. But this is usually more of a concern for
applications than for libraries. If my application spits out "IO error:
nonexistent file", I need to know which line it came from to know which file
it's talking about. But in a library it's usually more obvious.

~~~
monocasa
I mean, don't just bubble out errors until they lack context then. Rewrap them
to add more context like ConfFileReadError(io::Error).

~~~
shepmaster
That would be a major point of my library, SNAFU, discussed in the article.

[https://docs.rs/snafu/0.6.0/snafu/](https://docs.rs/snafu/0.6.0/snafu/)

------
mikedilger
I like file and line numbers with my errors:

    
    
      // No need to use this directly. See the e!() macro.
      #[derive(Debug)]
      pub struct ErrInfo {
          pub file: &'static str,
          pub line: u32,
          pub msg: &'static str  
      }
    
      impl std::fmt::Display for ErrInfo {
          fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
              if cfg!(debug_assertions) {
                  write!(f, "[{}:{}] {}", self.file, self.line, self.msg)
              } else {
                  write!(f, "{}", self.msg)
              }
          }
      }
    
      /// Use this with `failure` crate's `ResultExt` like this
      ///
      /// ```
      ///    $r = function().context(e!("Function failed"))?;
      /// ```
      ///
      /// Error context will then include that message, and the file and line number.
      #[macro_export]
      macro_rules! e {
          ($msg:expr) => {
              $crate::ErrInfo {
                  file: file!(),
                  line: line!(),
                  msg: $msg
              }
          };
      }

~~~
epage
Some of the highlighted error crates will include a full backtrace if you are
using nightly rust. Not sure when it will hit stable.

~~~
shepmaster
Some also produce backtraces on today's stable Rust and previous versions as
well. SNAFU can produce backtraces back to stable Rust 1.31.

------
epage
Rust brings some great features to the table but the combination of them opens
up a new level of challenges to deal with which is why there is so much talk
in the community on error handling libraries.

Some of the competing needs that need to be dealt with to understand why
include

\- Quickly create errors and extend them for prototyping while scaling to
programmatically reacting to them

\- Easily convert errors with `?` has drawbacks. `?` wants you to implement
the `From` explicit conversion trait but that becomes part of your public API,
exposing implementation details (switch dependencies or one bumps major is
technically a breaking change for you). Another problem is you can't both
implement the `Error` trait and support `From` from anything that implements
the `Error` trait because it will conflict with Rust's blanket implementation
of `From` converting yourself into yourself. We need specialization to solve
this.

\- You can return `Result<T, E>` from main but it prints `Debug` information
rather than user-friendly `Display`. So we need to deal with `Debug` printing
the right thing most of the time while printing something user-friendly in
`main`.

\- Rust users have their errors wrap underlying errors in a giant chain.
Rendering of this chain needs to be decoupled from a single `Error`'s
`Display`.

\- We aren't clean which errors in a chain are context and which are meant to
be actual root causes to programmatically act on

\- Supporting `no_std` for embedded cases.

On top of all of that are unsolved problems like process exit codes
specialized for the error being returned, debug-only information (e.g. opt-in
to rendering an original `errno` or `HRESULT`), error cloning (important for
caching failable-operations), error serialization (important for network
proxying APIs).

I think some in the Rust community are seeing Yoshua's comaprison and feeling
like we are converging on a solution. While I think that is true for
`derive(Error)` (code-genned trait implementations), I don't think this is
true for our patterns for how to structure and communicate content in errors
or how to do Dynamic errors. This post made me feel like Rust is in a rut.
People have taken a pattern or two from cargo's source and refined it without
branching out more into other ways of solving the problems. This ends up
leaving a lot of the problems i listed above unsolved or less than ideal.

I'm toying with ideas for an alternative approach for solving most of these
problems; just need to get some more time to get it in a shareable state.

~~~
couchand
> _Another problem is you can 't both implement the `Error` trait and support
> `From` from anything that implements the `Error` trait because it will
> conflict with Rust's blanket implementation of `From` converting yourself
> into yourself._

I believe this issue would only come up if you're trying:

    
    
        impl<T: std::error::Error> From<T> for MyError
    

which seems like a really clumsy way to handle it. In most real cases,
wouldn't you write the From impls for the specific types of errors you're
expecting?

~~~
epage
> which seems like a really clumsy way to handle it. In most real cases,
> wouldn't you write the From impls for the specific types of errors you're
> expecting?

In an application, yes. The problem is in libraries because the `From`s` are
public so you have put the error type for your dependency in your public API
and if you change dependencies or upgrade past a major, you've broken your
API. Granted, it is unlikely someone is going to use it ... but I wouldn't put
it past people to take shortcuts like that.

~~~
shepmaster
A benefit of SNAFU is that the `From` trait is implemented against the
internal-by-default _context selectors_. Since enum variants are public if the
enum is, SNAFU also offers a way to wrap an opaque newtype around the internal
error, allowing the implementor to choose exactly what public API their error
exposes.

------
wallacoloo
One error handling pattern I’d like to figure out how to implement in Rust is
where you trace the error _out_ from the point it was created rather than
capturing a stack trace at creation time. Tracing the error this way is
impactful when your application spans multiple processes or machines.
Client/server systems, for sure (you can know what the server did to error,
without extra noise in the trace from below where it received the command, and
you also get a trace of the client’s side. It’s substantially more useful when
the workload is distributed more.

The best I’ve come up with is using a custom ResultExt class which implements
std::op::Try, marks its trait methods as inline(always), and then captures the
program counter whenever you use `?` on it in order to build a trace. It’s not
the worst, but it still feels a bit odd — particularly the forced inlining
since that will surely break if you’re working with std::ops::Try from behind
a trait object.

~~~
AndyKelley
You can also make it never inline, and use the return address instead of
program counter. That's what zig does to implement this feature ("error return
traces").

~~~
wallacoloo
This would mean an extra function call for _every _ use of `?` (rather than a
conditional jump that the branch predictor has been well-primed for), even in
the non-error path. My Rust code for some projects use `?` pretty frequently,
to the point that I’d be concerned about perf. Of course, the only way to be
sure is to profile. Do you have any perf measurements with this technique in
zig?

~~~
AndyKelley
By default this feature is enabled in the Debug build mode only, so its perf
impact isn't a concern in release builds. But that being said, the function
call only happens in the error path, when returning an error from a function,
right? Are you concerned about the perf of the error path? Maybe I'm not
understanding something about your implementation. But yeah the way to be sure
is to profile. When I get around to it I'll add a flag to allow enabling this
feature in release builds and do some profiling.

~~~
wallacoloo
> But that being said, the function call only happens in the error path, when
> returning an error from a function, right?

std::ops::Try looks like: \- fn into_result(self) -> Result<Self::Ok,
Self::Error>; // called on `x` whenever you use the `x?` syntax. \- fn
from_error(v: Self::Error) -> Self; \- fn from_ok(v: Self::Ok) -> Self;

It's been a while since I explored this. I was thinking you'd
`#[inline(always)]` the `into_result` method, but now that I look at the trait
again, I think you would only force inline the `from_error` method and it
should still work but without impacting the non-error path much.

------
monocasa
I really don't like this whole "screw the type system, just throw string
errors that you don't know the type of" behind all of this. It feels like
backporting golang's error handling to rust, which feels like a step
backwards.

~~~
tyri_kai_psomi
That's a really unfair characterization of Go's error handling, and something
only really novice Go developers do.

[https://dave.cheney.net/2016/04/27/dont-just-check-errors-
ha...](https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-
gracefully)

~~~
TheDong
Every time you use `fmt.Errorf` and `if err != nil { return nil, err }`,
you're returning a stringly typed error up the stack.

The entire stdlib does this. An error stack is never present from stdlib
functions' errors, and third party libraries I see it just as infrequently.

If only novice go devs do that, than the entire go core team are novices, as
well as the authors of popular go projects like kubernetes and docker.

~~~
skybrian
"Stringly typed" means literally a string. Go doesn't always distinguish
between errors, but the error type does distinguish errors from non-errors and
that's important.

It seems better to have coarse-grained types than to standardize the wrong
type hierarchy?

------
timw4mail
While dealing with errors in any langauge is irritating, I think Rust has the
best ideas.

* Pattern-matching for success/error check

* Error passing with the `?` operator

* The correct error seems to appear when panicing due to an `unwrap` call

The most irksome thing remaining is to have to create your own error objects..

~~~
shepmaster
> The most irksome thing remaining is to have to create your own error objects

That's generally the purpose of the libraries outlined in the article —
reducing the pain of creating the custom error types.

------
bjoli
It bugs me to no end that Rust _almost_ got conditions and restarts. Apart
from proper macros, conditions and restarts are one of the few things that CL
has that no other language has mimicked.

Sure, I have seen it faked in scheme with call/cc, but that will never be a
proper, fast solution.

~~~
mst
I think guile at least got prompt and abort functionality which makes it
mostly doable?

Also, bizarrely,
[http://p3rl.org/Worlogog::Incident](http://p3rl.org/Worlogog::Incident)
exists.

~~~
bjoli
Not only doable, but rather efficient. Delimited continuations are superior to
undelimited ones in every way.

------
swfsql

      struct Error {  
        XAtLine47,  
        XAtLine88,  
        YAtLine13,  
      }
    

This way you have error AND error place as contextualization.

~~~
shepmaster
Like the sibling, I'm not sure if you are serious, but my library SNAFU
([https://docs.rs/snafu/0.6.0/snafu/](https://docs.rs/snafu/0.6.0/snafu/)) is
similar to this at a step or two higher level. The idea is that you wrap
lower-level errors (a file system error) with a higher-level context (opening
the config file).

Taken to the extreme, you have a unique context for every possible error
source inside your library, which means you could directly search for the name
to find the line number.

This does make me wonder how I could add line / file information to an error
automatically though.

------
skybrian
I'm wondering if there's any equivalent in Rust to how Zig does error traces?

[https://ziglang.org/documentation/master/#Error-Return-
Trace...](https://ziglang.org/documentation/master/#Error-Return-Traces)

------
jedisct1
I like the idea of TypeScript-like error handling.

Or more generally, functions that can return different types without having to
define an `enums`. If only because naming these single-use `enums` is hard :)

~~~
epage
Is it employing anonymous enums? They come up for helping errors in the Rust
community from time to time. The main dropback I see on relying on them is
that they make it easy to accidentally expose your implementation details as
part of your public API, making it a breaking change to upgrade or change a
dependency.

~~~
The_rationalist
In typescript you can return union types. Rust doesn't allow that, at least
not ergonomically.

------
loeg
As a Rust newbie, this is why I end up just using .unwrap() everywhere.
result::Result, io::Result, is something a Some/None or an Ok/Error, etc, etc.
What (non-string) errors does this function return, etc? I find it easier to
check for and propagate integer error numbers correctly in C.

~~~
unlinked_dll
One pattern is to use your own crate-level Error type and then Result::map_err
to bubble up other error types into your own thing. Example:

[https://play.rust-
lang.org/?version=stable&mode=debug&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=48252c0056e0a13829bb5d24171687ed)

This makes error handling a little more verbose but you can use some quick and
dirty macros for that.

~~~
shepmaster
> but you can use some quick and dirty macros for that.

That is the point of most of the libraries described in the article — to
prevent having to recreate this particular wheel each time you need it. Do all
Rust programs/libraries need one of the libraries? Not at all, but I find that
using one that has some nice bells and whistles will encourage the programmer
to improve the errors and their messages.

------
staticassertion
Box<dyn Error> for life

~~~
unlinked_dll
when your error handler can also fail

~~~
staticassertion
The top level error handler is

    
    
        inner_main().expect("it should work")

