Did Rust make the right choice about error handling?

Actually on Error Handling I'm with Rust.
When working with Operating System Libraries you far too often find that you are required to provide some complicated Structure to the Routines and actually never know what is the desired State of the Structure.
What later results difficult to check if the Results are correct or erroneous and what is required to get the correct Result.
This even more gets complicated as some Libraries don't provide a correct Initialization Routine

enum Status {
SUCCESS,
ERROR1,
...,
ERRORN
};
typedef struct S {
    uint a1,
    uint a2
    };
void queryData(S *struct_with_data);

Given this API how will you know that you got the correct values in the Fields struct_with_data.a1 and struct_with_data.a2
Does mean struct_with_data.a1 == 0 that queryData() has failed ?
But even struct_with_data.a1 == 0 can be a valid Data State.
Like for Example:
Lunix Signal Handling
the nice siginfo_t Structure.

On the other hand Rust does not let you continue with an invalid struct_with_data after queryData returned and Error Result.

The only Downside of this Error Handling is that it forced to much Result and Option objects with require a more and more Nested Code with on the long run becomes more and more unmaintainable and suffers in legibiilty.

For anyone coming from C I don't think it's controversial to state that error handling is worlds better in Rust. I'd say the same goes for Go, as that uses the same strategy for error detection and management as C.

I happen to think the same thing w.r.t. exception-based languages eg Java, C++ and C#, as exceptions 1. are not exceptional, 2. tend to be extremely slow control structures and so 3. the first 2 factors combine to produce the worst of both worlds.

However, given this very topic, there are still people who hold a different POV and somehow see exceptions as useful, but my educated guess there is that such cases are pretty much always instances of the Blub Paradox.

5 Likes

I think the usual arguments in favor of exceptions are:

  1. They can require less thinking about errors (for better or for worse), since errors aren't part of the function's return type. Rewriting a function to be fallible also takes less effort. (This could be mitigated a bit with syntax sugar, as withoutboats suggested.)
  2. They're useful if a situation is truly exceptional, since there's neither the syntax nor runtime overhead of error handling as long as we're in "the happy path".

Overall, I prefer Rust's approach.

2 Likes

Indeed I think that is the case as well. But handling errors properly when they occur (whether exceptional or not) is fundamental to building properly working software, and so neither can't nor shouldn't be optional regardless of the feelings of people involved. To do otherwise while the tools are not just available but ergonomic to use is lazy (in the negative way, not in the "this motivates me to make better abstractions" way) and irresponsible in my eyes.

As for the performance overhead for using Result-based error management, while it might not quite be zero even on the happy path (the Result types are instantiated after all) it is so close to zero so as to effectively not matter at all (a Result value being created is likely to be just some value being pushed on the stack at runtime, assuming the internal value already existed). I think that's acceptable even for embedded devices, given the rise of Rust in that space.

3 Likes

I would be interested to know what situations people would regard as exceptional and warrant handling with an exception mechanism like we find in C++.

I can think of very few possibilities, like:

  1. Running out of memory. Stack or heap.
  2. A bug in your code.
  3. Hardware error/failure.
  4. Power failure.

For 1) I'd say that in some situations that running out of memory is a bug in your code. You have not taken into account the resources available. Critical if you are building a resource constrained, safety critical, embedded system.

For all of these clearly the best thing to do is kill the program immediately. Get the bug/hardware fixed.

I can't bring myself to count user input errors or missing files or network outages etc, etc as exceptions. They are all input errors and all very common. As such I conclude C++ style exception handling is misguided. As I said above, it encourages sweeping error situations under the carpet and ignoring them. Whereas they are common situations and should be up front in your code. In extreme cases the "happy path" is exceptional !

What do you categorize as an "exception"?

3 Likes

If we keep going with this, it should be explicitly stated that Rust has panics, and panics have a lot of important similarities to exceptions. IMO it's most constructive to think of "traditional exceptions" as somewhere in between Rust panics and returning Results (or more generally, "monadic return types").

So it's probably very hard to think of a good use case for exceptions that isn't already covered by Results or panics, but that alone is not a good argument against exceptions. We have to look at error handling systems a little more holistically than that. Even surface syntax becomes almost critical here: Although I believe Rust has the best error handling story of any language I've tried, I would not believe that if we hadn't developed the try!() and ? syntaxes for Result propagation and instead needed to write an if or match block around every single fallible function call (this is actually one of my biggest complaints about Go).

4 Likes

On the topic of panics, it's idiomatic to panic in both of these situations today, and the behavior ends up pretty identical to an unchecked, uncaught exception in languages with exceptions.

For hardware error/failure, if it's something we're communicating with, I'd argue some nicer error message could be warranted (and the program could recover). If it's hardware we're running on, like ram or CPU errors, then I'd say all bets are off and killing the program is probably reasonable.

I quite like rust's separation of these two concepts - "recoverable errors" are in your face with Result, and unrecoverable ones, like program bugs, unconditionally unwind the stack and crash.

There's a bit of an odd situation where panics only crash a single thread, and not the whole program, but they can often be propagated through the program through poisoned mutexes and closed channels anyways.

1 Like

18 posts were split to a new topic: Error handling and UTF-8 validation

I have heard that argument a lot. I think it is obviously false. Consider:

A simple program to add two numbers can produce the correct answer or it can fail. One has to think about what do do in both cases. In the happy case we might want to print the result. In the sad case what? Crash an burn? Exit with nothing? Print the wrong result? Might be nice to think about it and do something helpful like print "Overflow error".

Sure exception keep the error out of the happy path so you don't have to think about it there. But you have to think about it somewhere no matter what mechanism you have to detect errors.

Scale that up to the thousands of errors that can happen in a large program and add the fact that we often want to keep running in the face or errors (Unless they are truly exceptional). There is no "less thinking" about it any which way.

Given all that we see that hiding the error situations under the rug with exceptions really obfuscates what you need to think about.

To my mind "truly exceptional" you cannot handle. Better to die immediately.

Exactly.

3 Likes

Just a quick semi-related question: What's the idiomatic way of returning a result with multiple errors in Rust? Should we do something like:

enum Errors {
    Whoops(String),
    Thats,
    An { location: usize, offset: usize},
    Error,
    ...
} 

type ErrorResult<T> = Result<T, Errors>

Or:

enum ErrorResult<T> {
    Ok(T),
    These(String),
    Are,
    AllErrors { kind: String, message: String },
    ...
}

The benefit of using a Result is the try (?) operator, but to me it seems like the latter method is more correct. Thoughts?

The first is the idiomatic solution. You make a single type for holding an error, and only errors.

5 Likes

As a Conclusion of the Analysis of the concrete Use Case of UTF8Error Handling as discussed at:

I found that the correct Processing and Recovery of the UTF8Error bloats up the Application Logic and thus produces a measurable increment of the Processing Time

BTW. Is there something preventing compilation of unhappy path of (at least some) code, which uses Results, into side-tables? Like it is done with exceptions.

I'm curious. Do you have a github repo or such that contains both Rust and whatever other language solutions to that problem? I'd like to see what is going on there.

I published all my Rust Code in the Playground Links.
So all commands and results of the analysis are fully reproduceable.

Yes, I know. I tried to follow that.

A single link to the best performing Rust and whatever other language solution would be great.

One needs both to make a valid comparison.

1 Like

The community suggested this version:

The version that produces the correct result is this one:

The test for correctness would be like this:

$ echo $(perl -e 'my @arrchrs = (226, 157, 164, 240, 159, 146, 150, 119, 250, 248, 240, 159, 146, 150, 247, 190); print pack "U*", @arrchrs; print "\n";'; date +"%s.%N") |target/release/text-sanitizer ; date +"%s.%N"

The Test Data is this Web Page as static Text file:

$ echo $(wget -S -O - "http://www.lanzarote.com/de/ausfluge" 2>&1) > lanzarote-com_de-ausfluge.html

Now the Question is: "Can the Parsing Function sanitize_u8() be rewritten in a way that it can produce the correct result in 200µs?"

That was actually the argument of the whole discussion.

The question to you is - can it be rewritten in any other language in this way? It seems that in C you've got the performance you want?

It's impossible domibay-hugo, I cannot fathom the exact requirement from that mass of code.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.