I've used error-chain, quick-error, failure but now with std::error::Error essentially being a marker trait, I don't use any of them. Instead of an "error crate", the derive_more crate lets me define and manage strongly-typed error types with no boilerplate at all.
Not having to type and maintain an impl Display block keeps things clean and succinct (custom Display trait can be impl'ed via #derive attribute when using derive_more).
Even foreign error declarations From impls can be handled automatically--it's hassle-free, very nice, and you still end up with strongly typed per-crate custom errors.
I think I may write up a blog post to expand on this, because it's an area that I think a lot of people are interesting in understanding better. But here's what it looks like in my "quickstart" (empty app/lib) template:
use derive_more::*;
#[derive(Debug, Display, From, PartialEq)]
pub enum Error {
#[display(fmt = "{}: {:?}", "msg::ERR_NOT_AN_INT", "_0")]
NotAnInt(std::num::ParseIntError),
}
Note:
-
#[derive(From)] attribute, above. Whenever I use a foreign type (std::num::ParseIntError in the example above), derive_more creates a From impl automatically.
-
#[display()] attribute, above. it includes the ability to custom format, reference const values, and error payload tuple fields (using the notation _n instead of .n)
There are many more features, but its the most succinct, expressive solution I've found. It even helps tackle problems like std::io::Error not implementing PartialEq, for example, although I'll save that for another post.
In the end, the error definition exercise becomes just listing the variant, any payload types you want it to accept, and a custom display formatter, if you want one)--this is what I've been looking for for a long time.
With this, #[must_use] on Result, the Option type and the error-check operator (?), Rust error handling is definitely "Top" for me as well. 