Live data from Hacker News

Rust error handling

bitfieldconsulting.com

41–50 of 65 posts

Re: Rust error handling

#41
post #38

Earlier quoted context omitted.

Lambdas broke checked exceptions. You can't declare that you throw whatever a generic lambda might throw, so they quickly devolved to "I throw nothing (only unchecked)." The "I throw everything" alternative is rarely used because it spreads virally through every caller.

Checked exceptions were strongly discouraged because they have nonlocal behavior when changing library code. If you want to rethrow exceptions you can’t handle you have to update all callers when the callee changes the throw signature. Lambdas are orthogonal.

I'm thinking of cases like Stream#flatMap where I might be prepared for what a lambda (probably a method ref) could throw, yet still can't use it because exception lists for the Stream interface methods had to be declared statically years ago.

Re: Rust error handling

#42
post #21

Earlier quoted context omitted.

We can disagree on it being overblown or not but I think it would be enormously useful to be able to look at a function signature and know exactly how it can fail.

They tried that in Java and no one uses it...

While Java usually gets the blame, it was following a precedent set by CLU, Modula-3 and C++.

Also I dearly miss them in .NET, every single time something breaks in production, because some developer didn't bother to catch exceptions, when they should have.

Re: Rust error handling

#43

Earlier quoted context omitted.

We can disagree on it being overblown or not but I think it would be enormously useful to be able to look at a function signature and know exactly how it can fail.

The ways in which a function can fail is typically the job of the documentation.

Why bother having a typed return value? That could be in the documentation too. The whole point of a type system is to help me understand what the function can and cannot do without needing to make guesses based on the documentation. It's not fatal, but it is annoying and inconsistent that Rust can do this on the happy path but not the error path.

Re: Rust error handling

#44

Isn't it based on ML family? I mean, I see Rust error handling heavy inspired in monads used in languages like OCaml and Haskell. Is Rust doing something different?

Rust's error handling isn't at all like using a monad. The entire point of being able to express the monad for something like the behavior you expect from error handling is that you write code which automatically propagates the error and in the same breadth prevents you from ever being able to see the error. The result is essentially exactly exceptions: you program the happy path and it entirely hides errors from you, as that's the point of the monad, which for this purpose you can pretty much conceptualize as programmatic flow control (or, even more crudely, an overloaded semicolon / statement separator operator).

Rust is using the data structure, but doesn't have a way to express or use the monad, so you have to deal with and manually propagate the error. But like, if you do not have the monad, the data structure is just awkward... the only reason the data structure for this monad even exists at all is to support the monad, and the reason for the monad is to get a syntax similar to exceptions! In a language with a ton of hard-coded syntax for all of these things you'd use a monad for in Haskell--whether it's error handling, asynchronous execution, scope allocation... whatever floats your boat--you should just use exceptions.

Re: Rust error handling

#45

Earlier quoted context omitted.

The ways in which a function can fail is typically the job of the documentation.

Why bother having a typed return value? That could be in the documentation too. The whole point of a type system is to help me understand what the function can and cannot do without needing to make guesses based on the documentation. It's not fatal, but it is annoying and inconsistent that Rust can do this on the happy path but not the error path.

The type system cannot capture 100% of the semantics of the function. You put what you can in there, but you also need documentation. You could provide a bespoke error type for every single function that returns an error, but that's a ton of boilerplate, and you're effectively just moving the documentation from the function to the error type (enum variant names are not sufficiently descriptive to avoid having to write documentation).

Even in cases where the error does already precisely match the semantics of the function, you still need documentation. std::sync::Mutex::lock() returns a `Result, PoisonError>>`. What's a PoisonError? That's a precise error type for the semantics of this function, but you need the documentation to tell you what it actually means for a mutex to be poisoned.

You cannot get away from having documentation. And you're free to make custom error types for all your functions if you want to, it just doesn't really get you much benefit over having a single unified error type for your module in most cases. If you have a reason to believe the caller actually does want to handle error variants differently then sure, make a new error type with just the variants that apply to this function, there's plenty of precedent for that (e.g. tokio::sync::mpsc::Sender has distinct error types for send() and try_send(), because that's a case where the caller actually may care about the precise reason), but most error values in Rust just end up getting propagated upwards and then eventually logged.

Re: Rust error handling

#46

Earlier quoted context omitted.

It depends on the program. You don't want your whole web server to crash because of a small error in one route. You probably don't want your CAD program to instantly crash and dump an error to the console because of a divide by zero in the constraints solver. Though in some cases like that I think it might be appropriate to use `catch_unwind()`.

If you're writing something safety critical like avionics flight control software, you probably don't want to crash in production either. I've also always interpreted "fail fast" as "Make defects obvious during development so they don't exist when you deploy to customers."

These are thought provoking counter examples.

So, on a micro-level, let's say I have a function that expects x to be a float between 0 and 1, and there's some math and logic built on this assumption. Of course, in development if this expectation is violated, we fail fast and loudly and then fix the problem. In production it's not quite so clear. But still, is it ever the right thing to ignore that an invariant is violated and just hope that things somehow work out?

> Make defects obvious during development so they don't exist when you deploy to customers.

I agree with this phrasing. From another viewpoint, I would say:

If your code checks an invariant, and that invariant is broken never (never!, not even in production) just continue on and hope that things will work out.

Re: Rust error handling

#47

Earlier quoted context omitted.

I think you’re misunderstanding the “fail fast!” advice. You want to fail fast in development not in production. In production you want maximum robustness. Users would rather see an error saying “whoopsie, maybe try that again later” than for the program to exit. That’s part of the reason why the functional error handling patterns are becoming so popular these days. They force you to handle errors and give you type-l…

You can catch panics.

I'm not sure that is something you can rely in general due to panic = abort, though to be fair that's more a concern for library developers since IIRC they don't control that particular setting.

Re: Rust error handling

#48

It's far from perfect. One of the biggest problems with Rust error handling is that if you want to have explicit error return types encoded in the type system you need to create ad hoc enums for each method that returns an error. If you only use a single error type for all functions you will inevitably have functions returning Results that contain error variants that the function will never actually return but still…

I'm just now learning Rust, as a long time C++'er, and this was the first part of my Rust journey where I thought to myself, "Boy, this really smells--this couldn't possibly be the idiomatic Rust Way to handle functions that can produce different types of errors. I must be doing something wrong!" For example, I have a function that takes an array of bytes, decodes it as UTF-8 to text, parses that text into an i32, an…

[deleted]

Re: Rust error handling

#49
post #34

Earlier quoted context omitted.

If you want to handle the assert cases, you should not use the asserts in the first place.

That isn't possible. The assert may not even be produced in code you control. The reason panics/exceptions exist is it is too onerous to handle every possible error condition at all callsites (allocation failure and broken pipes are the famous examples), and it is not possible to enumerate all possible error conditions (unintentional programmer errors for example). People have religious ideas about handling panics fo…

Panics shouldn't exist in modern Rust (if there is such a thing) at all.

Re: Rust error handling

#50

It's far from perfect. One of the biggest problems with Rust error handling is that if you want to have explicit error return types encoded in the type system you need to create ad hoc enums for each method that returns an error. If you only use a single error type for all functions you will inevitably have functions returning Results that contain error variants that the function will never actually return but still…

I'm just now learning Rust, as a long time C++'er, and this was the first part of my Rust journey where I thought to myself, "Boy, this really smells--this couldn't possibly be the idiomatic Rust Way to handle functions that can produce different types of errors. I must be doing something wrong!" For example, I have a function that takes an array of bytes, decodes it as UTF-8 to text, parses that text into an i32, an…

[deleted]
Post reply on HN