Hacker News new | ask | show | jobs
by d1plo1d 2256 days ago
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.
3 comments

https://blog.yoshuawuyts.com/error-handling-survey/ is a good summary of what's out there.

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).
The good thing with this_error is that it's just a custom derive on vanilla std::error::Error.
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.
As the kind of more conservative Rust user GP references, I've had a great time with the derive_more crate's From derive [0].

This lets me write code like this:

    #[derive(From, Debug)]
    pub enum SomeSpecificError {
        Io(io::Error),
        SomeAppLevelErrorCondition,
    }

    fn do_something() -> Result<(), SomeSpecificError> {
        do_some_io()?;
        // ...
    }
Previously I would have written:

    fn do_something() -> Result<(), SomeSpecificError> {
        do_some_io().map_err(SomeSpecificError::Io)?;
    }
I find this approach results in clear code that continues to compile and work just fine over time, even as everything changes around it.

[0] https://jeltef.github.io/derive_more/derive_more/from.html#e...

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.
I agree which is why I write SNAFU

https://docs.rs/snafu/0.6.6/snafu/

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 ).
Failure is really nice to use.

Might suit some people.

https://github.com/rust-lang-nursery/failure

I wouldn't recommend failure, its current maintainer and original author will be deprecating it:

https://boats.gitlab.io/blog/post/failure-to-fehler/

From the article:

> 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.