Live data from Hacker News

A guide to error handling in Rust

nrc.github.io

51–60 of 76 posts

Re: A guide to error handling in Rust

#52
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 set lints for cargo, for example to warn or even disallow compiling with any `unwraps` or `expect`s. I use cargo-cranky which makes using lints super easy, cargo doesn't yet have native functionality to set which lints should be enabled or disabled.

Re: A guide to error handling in Rust

#53
post #40

Earlier quoted context omitted.

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

What do you mean? It already errors out if you try adding them together because it requires explicit casts. What the comment you're replying to is saying is that it's better to just explicitly cast than figure out what the compiler guesses.

    let x=19/10+0.5
I want to decide for myself if my result is 2.4, 1.5, 1, or 2 in the example above

Re: A guide to error handling in Rust

#54

Earlier quoted context omitted.

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?

What do you mean? It already errors out if you try adding them together because it requires explicit casts. What the comment you're replying to is saying is that it's better to just explicitly cast than figure out what the compiler guesses. let x=19/10+0.5 I want to decide for myself if my result is 2.4, 1.5, 1, or 2 in the example above

The OP doesn't like that the error moves from the condition to the addition. And what I mean is that then just lint that there shouldn't be unions of number-types or so.

Re: A guide to error handling in Rust

#55

Earlier quoted context omitted.

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.

Maybe this is just about terminology.

But essentially, when it comes to union types, they behave like sets. The compiler merges them. (A | B) | (A | B) is the same as A | B. But for sum types (even anonymous ones such as tuples) the compiler can't merge them because that would lose information (if the result is from the first A | B or the second one). Instead, you end up with a nested structure.

Which one is desired depends on the use-case, but it's definitely different.

Re: A guide to error handling in Rust

#56
post #32

Earlier quoted context omitted.

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

For simple tasks you can get away without any external crates by using `Result >`. But it's much more comfortable to use thiserror or anyhow in the long run. fn read_string() -> std::io::Result { Ok("123".to_owned()) } fn main() -> Result > { 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>` 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.

Re: A guide to error handling in Rust

#57

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

what is the TL;DR difference between anyhow and Box ?

Re: A guide to error handling in Rust

#58
post #57

Earlier quoted context omitted.

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

what is the TL;DR difference between anyhow and Box ?

It's detailed here - https://docs.rs/anyhow/1.0.66/anyhow/struct.Error.html - the list is short but IMO significant

Re: A guide to error handling in Rust

#59

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.

Even if the "Ad hoc union" becomes a thing in Rust, you are not likely to get inference of return types. The return type is part of the function signature and Rust deliberately doesn't infer signatures, in languages with "too much" inference it's impractical for the human programmer to keep track of types because it's all inferred, this has started to be a problem in C++ as more and more things are auto. Rust has som…

While I don't strongly object to Rust's choice here, and I agree that production code should have a type signature on every function, I think this is more a place for lint/clippy/whatever. There's no need to gate the programmer trying something on them having produced a type signature that could be inferred.

Re: A guide to error handling in Rust

#60
post #27

Earlier quoted context omitted.

I know that this is a common wish, but anonymous sum types have pretty catastrophic impacts on type checking and lead to all sorts of bizarre corner cases like the following: let a = if cond { 1 } else { 1.0 }; a + 3 Now, the error would be pushed to the `+` operator because there isn't an `Add` for `f32 | u32`. Granted, this is a trivial example, and a programmer can easily see through it, but in general this can ge…

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

I think the ramifications of `Add` (and friends) for union types is interesting, but I think we can imagine alternatives that are clearly mistakes and so it seems like you're missing the point (... is my guess about the downvotes). Your last sentence makes an important point, though - relying on type inference always lets type errors propagate further than they would if everything was explicitly typed. Adding more annotations constrains that, although whether that would be sufficient is a more complicated discussion that's probably quite sensitive to the particulars of a given language.
Post reply on HN