I'm still annoyed that the Rust people screwed up error handling so badly. The designers should have gone with classical exceptions like other languages, but instead went for a fashionable-at-the-time combination of error codes and added exceptions (spelled "panics"), cribbed for some reason from Go. And on top of that, the Rust designers copied one of the most annoying parts of the C++ ecosystem: a compiler switch for changing exception behavior.
The overall result is that everyone pays the cognitive cost of exception (spelled "unwind" in Rust) safety, pays the syntactic and runtime costs of error code checking, pays the runtime cost of unwind tables, and still can't actually rely on unwinding to actually work, because anyone can just turn panics into aborts.
I hope for a language with Rust's focus on memory safety but without Rust's weird fashionable-in-the-2010s language design warts.
> The overall result is that everyone pays the cognitive cost of exception (spelled "unwind" in Rust) safety
Very, very few people have to think about unwind safety, because it really only comes into play when you're writing unsafe code, and relying on the ability for panics to be caught. Many folks aren't writing any unsafe, and many who are are doing it explicitly in a panic=abort environment. And most don't rely on panics being able to be caught in the first place.
So, some subset of library authors have to pay attention to unwind safety in some cases. This is hardly "everyone."
> pays the syntactic
It's one character.
> and runtime costs of error code checking,
Exceptions also have runtime costs. I'm not aware of anything demonstrating that there is a really major difference between the two in real systems. I would be interested in reading more about this! Most of the discussion I've seen focuses on microbenchmarks, which can be very different than real code.
> pays the runtime cost of unwind tables,
Only if you want them, as you yourself mention, you can turn this off. And many do.
I do think it's not quite what you're saying though, due to Rust's design, we would have checked exceptions, and so the Result part would be there with exceptions, it's the Ok() part that would change.
In theory you could make a language without checked exceptions that would make it be like your example.
Okay so, technically in theory you can introduce logic bugs, but not memory safety bugs, if you don't consider unwind safety in safe code. Logic bugs can happen in any code, of course.
The practical, day-to-day implications of this still round to zero, though.
> The practical, day-to-day implications of this still round to zero, though.
Vendors have been using similar language to downplay potential bugs for decades, usually to disastrous results. At one point, even memory safety wasn't a big deal. I'm just waiting for a software package to have a security vulnerability when an attacker is able to trigger an untested Rust unwind path and put some Rust daemon into a state it didn't expect.
There are many good parts of Rust, but I don't think I'm ever going to convinced that the error handling wasn't a huge and unfixable mistake. It's because error handling is such a big mistake that Rust has grown layers of syntactic sugar --- try!, !, etc. --- to paper over the ugly spot in the language.
Logic bugs can be just as disastrous as memory safety bugs. From an attacker's point of view, they're ultimately about making a program do something not intended. Downplaying logic bugs (where Rust is weak) and emphasizing memory safety (where Rust is strong) might make Rust look better, but it's not doing any favors for computing.
I agree that they can be. There are a few differences:
1. It is not clear that we will ever be free of logic bugs to the same degree that we can minimize memory safety bugs.
2. We're in a specific context in this sub-thread, and that's that you claimed that this is a pervasive issue that everyone must consider all the time. These logic bugs can only be introduced in a context that is very unusual, and so my claim is not that logic bugs in general are irrelevant, but that the context that this kind of bug can appear is smaller than you say it is.
> logic bugs (where Rust is weak)
Rust gives you way more tools than C to reduce logic bugs as well.
The whole point of secure programming is caring about those "unusual" contexts. And the comparison to C isn't really fair: C is dangerous for everything. Yes, Rust is better than C, and even better in some ways than C++, but my point is that there's another Rust out there, a Rust^, that's even better than Rust, and Rust^ uses exceptions for error handling throughout.
Most applications are not supposed to survive a panic!(), to the point where I personally tend to use "panic = 'abort'" for any code where performance matters. As such it's not really a mental overhead, and definitely not the Rust way of doing error handling (in the same way than using "assert" and a signal handler is not the way you're supposed to do error handling in C++).
The Rust way of dealing with errors is to return a Result<> and in my experience it's the best system I've used. It doesn't have the code flow breaking aspect of exceptions while being a lot nicer and less error-prone than C or Go style error handling.
Libraries can declare "I require semantic x or semantic y." Applications can as well. If an application says it needs semantic x, and a library says it requires semantic y, you get a compile error.
Most libraries do not depend on a particular semantic, in my experience.
I assume library authors can specialize code to either semantic as well, and carefully target both in a single library? E.g. with cfg attributes/macros.
I actually don't think so, but I'm also not sure what use case would require you to do this. Generally, you're agnostic, and only in very specific circumstances would you require unwind. I'm not sure when a library would require abort.
I can't come up with a case for requiring abort, but perhaps there are some performance optimizations possible when not unwinding? I assume the compiler does plenty already, but e.g. there would be no need to hang on to any data for making better error messages from a panic, since you can't capture it.
If Rust had gone with exceptions, we'd have lost the entire embedded community. You have to realize that -C panic=abort was added in no small part because of a hostile fork of the language that removed the unwinding mechanism.
I happen to like exceptions in principle, but for a lot of low-level embedded code they're a deal breaker.
The overall result is that everyone pays the cognitive cost of exception (spelled "unwind" in Rust) safety, pays the syntactic and runtime costs of error code checking, pays the runtime cost of unwind tables, and still can't actually rely on unwinding to actually work, because anyone can just turn panics into aborts.
I hope for a language with Rust's focus on memory safety but without Rust's weird fashionable-in-the-2010s language design warts.