Live data from Hacker News

Rust error handling

bitfieldconsulting.com

61–65 of 65 posts

Re: Rust error handling

#61

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…

You can still do that in rust if you want / need to:

https://play.rust-lang.org/?version=stable&mode=debug&editio...

    fn main() {
        match process(&[0x34, 0x32]) {
            Ok(n) => println!("{n} is the meaning of life"),
            Err(e) => {
                if e.is::() {
                    eprintln!("Failed to decode: {e}");
                } else if e.is::() {
                    eprintln!("Failed to parse: {e}");
                } else {
                    eprintln!("{e}");
                }
            }
        }
    }
    
    fn process(bytes: &[u8]) -> Result> {
        let s = std::str::from_utf8(bytes)?;
        let n = s.parse()?;
        if n > 10 {
            return Err(format!("{n} is out of bounds").into())
        }
        Ok(n)
    }
In library code though that would make it generally more difficult to use the library, so the enum approach is more idiomatic. Then that comes out as

    match(e) {
        MyError::Decode(e) => { ... }
        MyError::ParseInt(e) => { ... }
        ...
    }
etc, which is isomorphic to the style you miss. What you're perhaps missing the is that `except ...` is the just a language keyword to match on types, but that Rust prefers to encode type information as values, so that keyword just isn't needed.

I feel you on the larval stage. Once you get past that, Rust starts to make a lot of sense.

Re: Rust error handling

#62
post #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…

You can choose to work with Rust's Result in a monadic way, that's what methods like Result::and_then and Result::or_else and so on are for.

Because it's just another type you could also do whatever else you like, unlike with the Exceptions in typical languages which have them where too bad, we bolted the information to the control flow so now we're going on a journey.

If you want to bolt control flow to some information in Rust that's fine, feel free to define a function which returns ControlFlow::Break for success if that suits you, the try operator understands what you meant, early success is fine. Actually you can see this reflected in the larger language because break 'label value; exists in Rust unlike for example C++.

Re: Rust error handling

#63
post #2

Maybe not perfect, but it seems to work out better than exceptions. Exceptions are a good idea which turned out to be too complicated. A language has to use destructors to clean up for almost everything for this to work. "?" has no "catch" clause within the function. So if an object has an invariant, and that invariant must be restored on error, the destructors must restore the invariant. If that just means unlocking…

This recent post resonated with me: https://cedardb.com/blog/exceptions_vs_errors/ There are certain obvious (and some less obvious) benefits to both exceptions and results, but I get the impression a lot of programmers have overreacted against exceptions. Exceptions "just work" the same in every codebase and require little boilerplate in most languages. I think results really shine for internal business logic where…

That's what panics are for. They compile to the same code c++ exceptions would. You can unwrap your results to turn them into exceptions.

Re: Rust error handling

#64
post #44

Earlier quoted context omitted.

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…

You can choose to work with Rust's Result in a monadic way, that's what methods like Result::and_then and Result::or_else and so on are for. Because it's just another type you could also do whatever else you like, unlike with the Exceptions in typical languages which have them where too bad, we bolted the information to the control flow so now we're going on a journey. If you want to bolt control flow to some informa…

What makes Monad interesting is that it is a trait that you have implemented, so you can work generically any monads. I thereby feel like saying manually calling these methods is "monadic" kind of misses the point of why a monad was interesting in the first place.

Haskell's error handling isn't you sitting around calling monad methods: you implement the Monad trait so you don't have to, and it then all gets hidden behind do notation, with the result that you get the same control flow that you'd get just using exceptions.

It thereby is just constantly strange to me that people talk about any of these languages as if they learned something from Haskell... Haskell clearly wanted things like state and exceptions and such, but wanted to do so on top of a lazy pure functional core language.

The trick they came up with is thereby to define this trait called Monad, which lets you program into the control flow all of these bespoke behaviors you get from the imperative languages: state, exceptions, scopes, asynchronicity, list comprehensions... you name it.

But the end result is not in any way "manual": the end code doesn't involve destructuring an Either every time you make a call, but it also doesn't involve calling methods to deal with the errors. The end result is, as best as they could implement, exception handling!

And like, in the same way that people can make mistakes with manual memory allocation, so we prefer scope allocation, people can also make mistakes with manual error propagation, and so you'd expect we would prefer exception unwinding: the monad enforces consistency.

In that light, the behavior of most languages with respect to many of these behaviors is to just have hardcoded every function to be in a standard set of stacked monads for basic things everyone takes for granted: exceptions are just hardcoded monadic error handling.

But Rust? That isn't monadic error handling: that's just manual error propagation. If we are going to call Rust's manual error management regime "monadic", we should also call C's manual resource management regime "monadic". If you are doing it manually, it isn't monadic.

And sure, calling the methods of the monad kind of makes it look a bit less manual, but that's like moving on from C and now saying that Go's manual-ish resource management (defer) is monadic. If you aren't forced to do it the standard/correct way, it isn't monadic.

Re: Rust error handling

#65
post #20

Earlier quoted context omitted.

Zig has automatic error unions. No boilerplate at all, but not just a single "error" type. The only downside I see in zig errors is that they can't hold extra data.

It's a massive downside. When I was using the JSON parser I found it very annoying that it could only tell me the input JSON was invalid, not where in the input the problem was.

Yeah I agree. I think they tend to go for a mutable parameter reference to keep track of that stuff, which is definitely C-like but kinda unwieldy.
Post reply on HN