Hacker News new | ask | show | jobs
by martijnarts 1311 days ago
For anyone interested in what this would look like in Rust now, there's two ways. For libraries, people tend to recommend the thiserror crate. Code sample[0]:

    #[derive(thiserror::Error, Debug)]
    enum Error {
        #[error("One")]
        One(#[from] Error1),
        #[error("Two")]
        Two(#[from] Error2),
    }

    fn foo(r1: Result<i32, Error1>, r2: Result<i32, Error2>) -> Result<..., Error> {
        let i1 = r1?;
        let i2 = r2?;
        // ...
    }
Whereas for binaries, people usually recommend anyhow. Code sample[1]:

    fn foo(r1: Result<i32, Error1>, r2: Result<i32, Error2>) -> anyhow::Result<...> {
        let i1 = r1?;
        let i2 = r2?;
        // ...
    }
[0]: https://play.rust-lang.org/?version=stable&mode=debug&editio... [1]: https://play.rust-lang.org/?version=stable&mode=debug&editio...
3 comments

The binary vs library thing seems like an oversimplification to me. I think it's more like: do you need callers to handle this error specifically? With a library the answer is "I don't know, better let them do it", so you don't want anyhow. But in a binary, you may or may not, and it depends on the error.

The pattern I use in my app is to use thiserror, and then just have an anyhow catch-all. That lets me do specific stuff where I know I'm going to need specific handling, and an easy-to-use fallback for just saying "this bad thing happened" with the anyhow! macro.

    #[derive(Debug, thiserror::Error)]
    pub enum Error {

        #[error("Not logged in")]
        NotLoggedIn,

        #[error(transparent)]
        Api(#[from] ApiError),

        // etc

        #[error(transparent)]
        Other(#[from] anyhow::Error),
    }
I don't know if this is the best pattern but it's worked really well for me.
For simple tasks you can get away without any external crates by using `Result<T, Box<dyn Error>>`. But it's much more comfortable to use thiserror or anyhow in the long run.

    fn read_string() -> std::io::Result<String> {
        Ok("123".to_owned())
    }

    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let s = read_string()?; // io::Error
        let n = i32::from_str_radix(&s, 10)?; // num::ParseIntError
        println!("read number: {n}");
        Ok(())
    }
Except you often need `Result<T, Box<dyn Error + Send + 'static>>` if you go that route. At the very least, you should create a type alias for it. I very much prefer the use of `anyhow` and/or `thiserror` depending on if I need typed errors.
And the reason it works in anyhow is ... They have those conversions: https://docs.rs/anyhow/latest/anyhow/struct.Error.html#impl-...
what is the TL;DR difference between anyhow and Box<dyn Error> ?
It's detailed here - https://docs.rs/anyhow/1.0.66/anyhow/struct.Error.html - the list is short but IMO significant