Announcing error-chain, a library for consistent and reliable Rust error handling


#11

Oh, yes, absolutely. (That’s actually a much better statement than my “only of interest for debugging fatal errors”–I think what I’m really trying to get at is unexpect errors should be fatal if possible.) But how can an error be unexpected if it’s part of the signature of a method? If you don’t expect a particular error condition, panic when you see it (again as per Duffy’s advice). In my ideal world, once a software product is ready for release, errors that can be handled are, while errors representing true unforseen bugs and conditions that were (incorrectly) assumed to be impossible are “handled” by panicking (if indeed they’re handled at all, since the whole point is that this category of errors can’t be predicted in advance).

I suppose I can think of a very few cases where generating some backtrace information and propagating it via error-types might be useful, even in release mode. One is if there’s some way to panic inside the error-handling code several levels up the call-chain; in this case you’ll lose some potentially-crucial backtrace information if the “semi-happy-path” error-recovery mechanism doesn’t propagate the trace info “just in case”. But that seems (prima facie) like a pretty limited scenario to me.


#12

Awesome! I just converted one of my codegen projects with a bunch of different possible error types over to error_chain, it was really simple. Not a single impl Display in sight :smile:


#13

Glad to hear it!


#14

I just wanted to add my kudos, this is excellent. I started working on a much lamer version of this macro, and am in the progress of switching. Excellent job.

A note to other beginners, since this generates so many structs and impls, you might be wondering (like #[derive(…)]) how to “view” the structs. The easiest method i’ve found is to rely on cargo doc --open and then use that to browse all of the code interfaces. I’ve started using this instead of just jumping around to code at this point.


#15

Okay, I’m here again seeking for alternatives.

Actually, we already have error chaining in quick-error using into() and context(), and adding traceback manually is easy: here is a full example and a diff to example without tracebacks. The interesting part of example:

quick_error! {
    #[derive(Debug)]
    pub enum Error {
        Io(err: io::Error, path: PathBuf, tb: Backtrace) {
            display("could not read file {:?}: {}\n{:?}", path, err, tb)
            context(path: &'a Path, err: io::Error)
                -> (err, path.to_path_buf(), Backtrace::new())
        }
...

What’s notable here:

  1. You can just add a var and a Backtrace::new() call. Easy.
  2. You can add backtrace only for some cases, i.e. unexpected errors. Other conditions don’t have to pay for boxing.
  3. The display handler is obviously wrong (includes a traceback), it’s just for showcase here
  4. Traceback objects may be more complicated like Option<Arc<Backtrace>> but in can be type-aliased. And constructor (which checks environment, for example) might be just plain function, no need for a complex macro.

What we want is a pretty printer which walks the causality chain and prints errors with tracebacks. Making a pretty printer is easy as long as we have a trait that exposes all needed information.

Ideally, we would have Error::traceback() in the std::error::Error similar to Error::cause.

If we are out of stdlib we may add a TracebackExt trait:

trait TracebackExt {
  fn traceback(&self) -> Option<&Backtrace>;
  fn cause(&self) -> Option<&Error + TracebackExt>;
}

It’s easy to get this trait implemented in a way similar to how cause works in quick-error (the only issue is that I don’t wan’t quick-error to depend on backtrace crate)

What do you think?


#16

For those interested, I just converted to using this library (took a while, I had a lot of errors defined!)

A couple of things I noticed:

  1. you will run into: error: recursion limit reached while expanding the macro 'quick_erro', the limit of errors looks to be 10. Edit: it looks like this is inconsistent, 10 in some cases, others allow more, may be some other bug that I’m missing.

  2. I’ve run into an issue where into() isn’t really good enough, especially when using chaining. I decided to just do Error::from(ErrorKind::Kind) where needed.


#17

I published a new build, 0.2.1. The only change is that now error chain lives in its own repo, so the doc links have changed.


#18

I think I’d like to log a message at the point where the error is first noticed (eg, soon after the failed syscall), even if it’s later handled short of terminating the program. I have some boilerplate to do this today, which could potentially be converted to macros. Is this handled by error_chain?

(Apologies if this is in the docs somewhere and I missed it.)


#19

It doesn’t today but it would be easy to add at the same place the backtrace is being generated. Patches welcome!


#20

I’ve just released error-chain 0.2.2. In this release the types { } section may be omitted, and I have added a quick start section to the README.

0.2.2

Contributors: Brian Anderson, Jake Shadle, Nate Mara


#21

I’ve just discovered that error-chain seems to not work on 1.10 stable: https://github.com/brson/error-chain/issues/12

This is surprising to me since at one point rustup did build on stable.


#22

error-chain 0.3.0 is out. In this revision, foreign errors are stored in the ErrorKind variant instead of boxed into Error's ‘cause’ field, and the Display impl shows output of the foreign error’s Display impl instead of the simple description.

0.3.0

Contributors: Brian Anderson, Taylor Cramer


#23

There’s a PR open against error-chain that introduces a major breaking change, and I’m curious what people think.

So after the last release that changes how foreign links are represented, their ‘description’ is basically useless. As a reminder the description is the string here:

error_chain! {
    ...

    foreign_links {
        ForeignError, ForeignErrorKind, "description"
    }
}

So this PR removes it completely, with no backwards-compatibility considerations. My inclination is to just do it, along with a major version bump (0.3 -> 0.4 - cargo considers minor bumps less than 1.0 to be incompatible). ISTM that this library is immature enough, and has few enough users that when they do decide to upgrade it’s not such a big deal to make the changes. But it may be possible to have the macro continue parsing the description and throw it away. Any opinions?


#24

I think this breaking change is a good idea: I’ve personally never been 100% sure as to the purpose of the description. It somehow always seemed “extra” to me in the foreign_links section. And as the PR author mentions, fixing the code that’ll break because of this change isn’t too difficult.


#25

I’m entirely in favor of this change. Please document it in the README as a release note for people upgrading from previous versions, but otherwise, this seems like a great refinement of the library.


#26

error-chain 0.4.2 is out. In this release the foreign_links no longer take a description, and all sections are optional.

Note though the implementation of optional sections is lacking. It allows you to duplicate the section, and if you do then things will go bad. If anybody has an easy fix for this please help me!

You’ll also see below that I failed to release this twice… Long day.

0.4.2

Contributors: Brian Anderson

0.4.1 (yanked)

Contributors: Brian Anderson

0.4.0 (yanked)

Contributors: Brian Anderson, Taylor Cramer


#27

I made a pull request.


#28

Thanks @birkenfeld!


#29

error-chain 0.5.0 is out. In this revision backtraces are only collected when RUST_BACKTRACE is on, which brings behavior inline with the upstream panic backtraces and works around performance problems present in Windows backtrace generation. This means that the backtrace is stored in an Option now, making this a breaking change. It may be worth thinking about additional programmatic ways to turn on backtraces. This also improves handling of repeated ‘sections’, and the ‘types’ section now must not repeat.

0.5.0

Contributors: benaryorg, Brian Anderson, Georg Brandl


#30

error-chain 0.7.1 is out. This release reverts some changes from the 0.6 series that caused regressions in inference and usability. It also comes with a new bail! macro, syntax sugar for early return with type conversions.

The main things to be aware of compared to 0.6:

Declaring links { } again requires the ErrorKind name:

links {
    Utils(::utils::Error, ::utils::ErrorKind);
}

The ResultExt trait is once again defined by the error_chain! macro, not in the error_chain crate, and so must be mentioned in the types { } block:

types {
    Error, ErrorKind, ResultExt, Result;
}

The Error kind is again a tuple, not a struct, and is matched like a tuple:

match Error::from("error!") {
    Error(ErrorKind::InvalidToolchainName(_), _) => { }
    Error(ErrorKind::Msg(_), _) => { }
}

And the bail! macro:

fn foo() -> Result<()> {
    if bad_condition() {
        bail!(ErrorKind::FooError);
    }

    Ok(())
}

Which also works with fmt specifiers:

fn foo() -> Result<()> {
    if bad_condition() {
        bail!("bad stuff happened: {}", bad_stuff);
    }

    Ok(())
}

The bail! macro is inspired by a similar macro in Cargo and a similar throw construct in the exception handling RFC.

Finally, thanks to @Yamakaky for taking on some of the error-chain maintenance responsibilities in the last few weeks.

Contributors: Brian Anderson, Yamakaky

0.7.1

0.7.0