Live data from Hacker News

A guide to error handling in Rust

nrc.github.io

41–50 of 76 posts

Re: A guide to error handling in Rust

#41
post #38
post #30

This glaring omission from this is the "enum idiom": https://doc.rust-lang.org/std/convert/trait.From.html#exampl... they talk about it here: https://nrc.github.io/error-docs/error-design/error-type-des... but including more than a snippet would go a long way to that "aha" moment I think. This was frustrating for me browsing this site. The author wrote 10 pages of docs, but nearly all the examples are like 5 line sni…

Whats the "aha" moment for it, the Froms? The author might not have included that as they call out you likely shouldn't directly wrap another error. I go a step further and think that public errors shouldn't have From's for concrete types, exposing your implementation details, and that enum errors are more generally too tied to implementation details to be used in libraries.

OK, but what do you do then? Its not really helpful to say "this bad", if you don't offer a "this good". Of the maybe 10 approaches I have seen to Rust error handling (including using external crates, gross), the "enum idiom" is the most elegant and flexible to me, and coming from another language feels the most natural.

Re: A guide to error handling in Rust

#42

I feel like this document makes the Try operator (?) and its associated trait more mysterious than necessary. Most people probably won't need to implement Try, especially before it is stabilised, but it's not that much more complicated than say, AddAssign the trait which you implement to make the Add Assignment (+=) operator work on your type. The key trick of Try is that it converts something (by default an Option o…

I do some hobby projects in Rust.

One gotcha that I hit was using ? in sample code in documentation. It didn't work, so I had to replace all of my ? with .unwrap().

I generally consider .unwrap() a poor example, because it encourages writing code that could crash a program unnecessarily.

Re: A guide to error handling in Rust

#44
post #14

It is perhaps too verbose by default, as indicated by popularity of thiserror and anyhow crates.

It can be as verbose as you want. This guide as well as others suggests using concrete error types for libraries but anyhow "catch all" method for applications.

There is disadvantages for going "catch all" libraries as then you can't be sure you are catching all errors.

Re: A guide to error handling in Rust

#45
post #42

I feel like this document makes the Try operator (?) and its associated trait more mysterious than necessary. Most people probably won't need to implement Try, especially before it is stabilised, but it's not that much more complicated than say, AddAssign the trait which you implement to make the Add Assignment (+=) operator work on your type. The key trick of Try is that it converts something (by default an Option o…

I do some hobby projects in Rust. One gotcha that I hit was using ? in sample code in documentation. It didn't work, so I had to replace all of my ? with .unwrap(). I generally consider .unwrap() a poor example, because it encourages writing code that could crash a program unnecessarily.

You can use it in sample code in the documentation, but you will need to add a bit of boilerplate around it: https://doc.rust-lang.org/rustdoc/write-documentation/docume...

Re: A guide to error handling in Rust

#46

Too bad Rust doesn't have union types (aka adhoc / anonymous unions) yet. Without them, using typed errors is very clumsy. Optimally, you would write the following code: fn foo(r1: Result , r: Result ) { let i1 = r1?; let i2 = r2?; // ... } and Rust would infer the return type to be Result without having to do any extra definitions or conversions.

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 , r2: Result ) -> Result { let i1 = r1?; let i2 = r2?; // ... } Whereas for binaries, people usually recommend anyhow.…

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.

Re: A guide to error handling in Rust

#48

Earlier quoted context omitted.

I think it's the job of the IDE to make that work, but I agree, without IDE this can quickly become a problem.

I think method signatures are part of application/typesystem design and should not be inferred. Explicitly provided types are a feature. Inferred/auto type signatures are "necessariy evil" to reduce boilerplate type declarations around code. While codeblocks `fn1(fn2(), fn3)` and `var r1 = fn2(); var r2 = fn3; fn1(r1, r2)` are more or less identical, unless you have static type definitions for these methods you start…

> unless you have static type definitions for these methods you start having a very bad time inferring what types are being passed around.

It doesn't matter if the types are annotated explicitly or inferred. The amount of information is 100% the same. The IDE could just fill in the types _exactly_ in the same way as they would look like when annotated by hand - maybe just with a different color.

IntelliJ does this quite well, see for instance: https://i.stack.imgur.com/tiqjc.png

This is not a guess. This is the real types. There is literally no difference in the behaviour/semantics of the code.

> Consider typical python wrapper library with liberal

No, because python is not statically typed. You can't compare that.

Re: A guide to error handling in Rust

#49
post #40

Earlier quoted context omitted.

> Now, the error would be pushed to the `+` operator because there isn't an `Add` for `f32 | u32`. Why not? It makes total sense for one to exist. This is something the language needs to deal with, not the programmer. But the programmer always sprinkle annotations so that errors can only reach so far.

> It makes total sense for one to exist. I disagree: IME, when you add a float and an integer, you want to cast float to integer 50% of the time, and integer to float the remaining 50% of the time. Even if it leads to more verbosity, I prefer arithmetic operations to be endomorphisms and use explicit casts.

I'm sure you can just use a linter or configure the compiler to error out in such a case if there are really no use-cases, no?

Re: A guide to error handling in Rust

#50

Earlier quoted context omitted.

Unions don't have a discriminant. Anonymous Sum types have a discriminant, you just can't name it. Unions in Rust are unsafe because you can't tell what the underlying value will be.

Well, that depends. If the union consists of two types that share the same underlying structure, then obviously at runtime we can never know what the value is. But otherwise we can. And this is something that we will know at compile-time, so we can prevent runtime-checks that would not work.

I must not be understanding what you're asking for because it sounds like anonymous sum types.
Post reply on HN