As someone newer to Rust who has been using error-chain because it was what I found at the time I'd be curious to hear what your preferred solution to errors in modern rust is.
I think current rough consensus is anyhow/thiserr depending on if you're writing an application or library. I haven't actually used them myself, though. You don't have to keep up with the cool new libraries.
Agree that there’s consensus on anyhow for applications, but I think many folks now prefer vanilla std::error::Error for libraries (which itself got better as a result of all the experiments in the ecosystem).
I used to maintain a custom derive crate for errors before failure came out, but these days I just use manual impls of `std::error::Error` for libraries and `Box<dyn std::error::Error>` for applications. I can't be bothered to keep up with the Rust error-library-du-jour game, even if it so happens that today's darling custom derive (thiserror) would emit the same `std::error::Error` impl that I write by hand.
Manual impls may be tedious to write, but they change rarely after they've been written, enough that the con of adding one more dependency that may go away tomorrow outweighs the con of writing them manually.
This matches my experience as well. I want to write code that works for years (dependencies make this hard) and is understood by everyone. So explicit is best.
Unless you want all I/O errors to be indistinguishable to your callers, you should use more specific variants than a single `Io(std::io::Error)`, like `ReadConfig(std::path::PathBuf, std::io::Error)` and `ConnectToServer(std::net::SocketAddr, std::io::Error)`. And if you do so, the `From` impl becomes untenable and you have to fall back to `.map_err(...)?`.
In general, I feel `?`'s automatic `From::from` conversion leads to bad errors, because the programmer is incentivized to have type-specific context via implementing `From<>` rather than operation-specific context, even though operation-specific context leads to better error messages. Consider the difference between:
failed to initialize library
caused by: an I/O error occurred
caused by: no such file or directory (2)
and
failed to initialize library
caused by: could not read config file at /path/to/config.toml
caused by: no such file or directory (2)
Edit: I should add that it is possible to use `From<>` when the type-specific contexts and operation-specific contexts form a bijection. In the above example, if there were dedicated `struct ReadConfigError(std::path::PathBuf, std::io::Error)` and `struct ConnectToServerError(std::net::SocketAddr, std::io::Error)` types, then having the higher-level `Error` impl `From<ReadConfigError>` and `From<ConnectToServerError>` would be perfectly fine. Of course, that would just shift the issue down to those types since they would not be able to impl `From<std::io::Error>`. Also, in my experience, libraries rarely bother having such individual operation-specific error types anyway.
The selectors approach is clever, given that it works without a `map_err` closure but still supports borrows through the `Into` conversion. Unfortunately, all the libraries before yours have ruined the chances of me using anything outside of libstd as far as error-handling is concerned ( https://news.ycombinator.com/item?id=22820661 ).
> The crate I would recommend to anyone who likes failure’s API is anyhow, which basically provides the failure::Error type (a fancy trait object), but based on the std Error trait instead of on.
I think current rough consensus is anyhow/thiserr depending on if you're writing an application or library. I haven't actually used them myself, though. You don't have to keep up with the cool new libraries.