This may be common knowledge, but it came to my attention today, so I figured I would mention it.
The error-chain crate, once a relatively common way to implement error types, has not been updated since last June. Pull requests have accumulated.
If somebody cares about this project enough to do the maintenance work, consider asking someone on the Rust team to make you a maintainer.
Otherwise, it would be nice if someone with access would update the docs to indicate the project is unmaintained and move it to rust-lang-deprecated.
New projects should probably not use error-chain. failure provides most, if not all, features that error-chain does, with a much cleaner interface, and is supported by the Rust team.
Note that failure itself, while maintained, is still in a bit of a flux. Specifically, there are no immediate plans for 1.0 release. So, while using failure is probably better than using error-chain, it's not a clear-cut choice either. This comment provides a nice summary of the current state of affairs in error-handling.
I've used error-chain in most of my projects. I noticed it was producing deprecation warnings since 1.33, but had assumed they would be fixed.
Has anyone written a porting guide from error-chain to just the normal error handling? (I don't want to switch to failure or any others and get burnt again!)
My main reasons for using error-chain are that I can use ? for errors of different kinds in the same function, and for the bail! macro which I use a lot.
Also in BurntSushi's post he says "The downside to using Box<Error> is that it's probably not something one wants to use in a library." So I wonder what should one do in a library with just the built-in error support?
Personally, I would not attempt to write Rust error types without external library support. There's just too much boilerplate-y junk to consider.
A lightweight alternative to error_chain! and failure is the error_type! macro that I've seen in a few projects. I don't have much experience with it, but it seems to do the job.
But the way you do it is with the same patterns the various libraries tend to help with: define an enum for you local error type, and as external error types enter the project and need to be converted with ?, add a new variant that simply wraps that type, along with a From impl.
etc. Managing the descriptions is going to be a pain. Managing the cause chain is going to be so hard that it's not worth doing, but if you did want to add extra semantics to a wrapped error it could be something like
I used to use error-chain and failure, but I tend to just write my errors directly now, so I have control over how they work. A simple error would be something like
If you’re writing a library and your code can produce errors, define your own error type and implement the std::error::Error trait. Where appropriate, implement From to make both your library code and the caller’s code easier to write.
The blog post contains at least a few examples of this.
Today, I would probably not use any error handling helper library for a library. Generally, I do not think defining your own errors requires that much boiler plate. Defining the error variants themselves, along with how to convert them and how to show them, are all important aspects of your public API. (And perhaps more importantly, paying careful attention to this is crucial for choosing what exactly you expose. It can be very easy to accidentally incur a public dependency on another library by re-exporting one of its errors or exposing a From impl. It may instead make sense to not export such errors.) The "boiler plate" part of it is really the bits and pieces around it, e.g., writing out the impl From<...> for ... {}, writing out the impl fmt::Display for ... { } along with the corresponding match expressions.
I've done this many times myself. Here are some of them: (Some of this code still contains impls of cause or description, but you can drop the latter, and should implement source instead today, if pertinent.)
FWIW, I've recently switched from failure to regular std errors and cannot report many problems, except the lackluster behavior of Box<dyn Error>.
Since I'm not a library author, I can afford using err-derive (derives Display and Error) and derive_more (derives Display, From and much more) to deal with the boilerplate. Though I agree that the boilerplate is generally manageable even without these.
I've now switched to my own error module but I notice that it is slower than error-chain and I don't know why.
It is in the source for retest's xerror.rs file which I've also copied below since its only 100 lines.
Is there any way to make it faster (without making it too complicated!)?
I can't see any obvious problems with your code, so I can't say why the new version is slower. It'd probably help if you could show a comparison between the error-chain version and the new std-based one, together with some measurements that show the slowdown.
That being said, I think any concrete issues with migrating from error-chain are probably a little off-topic here. You might want to open a new thread with the details on your problem that I mentioned above.
So why not use Failure for libraries? It is that the failure::Failure trait becomes part of the public API of our error types, and we don't know if Failure is going to be around long term?
Because, for me, it's not just about the boilerplate. I like to wrap up underlying errors with ResultExt::context and I want backtraces for every underlying cause.
Firstly, as someone who appreciated error_chain when it was released, thank you @brson for building it, I personally benefited from learning from it's usage.
Second, for those saying that they're avoiding failure and using standard types, I'm curious if you support backtrace? This is my primary motivation for using failure at the moment.
Not in libraries, no. This is usually because libraries have targeted errors, and it's pretty easy to see where the error comes from.
For applications, sure, that's why I mention failure::Error in my blog post: its backtrace support is very nice. (snafu also supports this, which I'm in the process of experimenting with in ripgrep core.)
If NestedLibrary has not captured a stack trace at the location of the error, the best you can do is hope that CoolMetaLibrary did. If it hasn't, then your trace will only point to the location in MyApp where you call into CoolMetaLibrary, which you probably already knew.
I actually hope for a world where error types are fine-grained enough to identify a line of source code from a specific error, but I fear that such a world isn't easily attainable.
I do think it is a potential problem in theory, and I still think it's good for the error ecosystem to support it.
Honestly though, even though I introduced backtraces into errors in error-chain, I have never used error backtraces for any purpose. I have to imagine that people supporting large codebases would find it useful, as long as they are threaded all the way through the chain, which they almost assuredly will not be considering the fractured state of the ecosystem.
Imagine that NestedLibrary error contains some path to the file that is missing or corrupted. If CoolMetaLibrary error does not point to the NestedLibrary error, then it may be hard to guess that MyApp error is caused by the corrupted file.
It happens to me all the time.
This is a different problem than the one that a backtrace solves. A backtrace will identify the line of code the error occurs at and all the intermediate functions. It will not include the file name you attempted to open.
“Nesting” errors does include what you are talking about, and most (all?) error management libraries perform that goal. ( of course, I think SNAFU is best )