4 ms·
More hacks upon hacks to make up for the glaring inability of the Rust people to acknowledge they made a mistake by leaving out exceptions. Just like the "type
by quotemstr 5y ago
More hacks upon hacks to make up for the glaring inability of the Rust people to acknowledge they made a mistake by leaving out exceptions. Just like the "type after identifier" thing, the use of error values instead of exceptions was a pointless piece of Go envy in fashion at the time that we're all going to have to live with for decades.
Plus, Rust doesn't even get to avoid paying for exceptions, because it still has panics and still has to support unwind semantics --- even though they can be disabled at build time.
Avoiding exceptions was a needless technical error and an illustration of why we should design systems around tried and true techniques instead of jumping on fashion trends and ossifying those fashion trends into immutable mediocrity.
- shepmaster 5y agoWhich language(s) did exceptions the best, in your opinion?
- bartwe 5y agoI'd say c#, though the lack of a hard 'nothrow' constraint is annoying.
- fouric 5y agoNot OP, but: I think that there's a pretty strong case to be made that conditions, implemented in Common Lisp, are the best implementation of "exceptions"...by not actually being exceptions. Conditions (plus restarts) have two advantages over exceptions (as implemented in almost every other language, including Go and C#) and Rust's error system that neither can replicate: they allow you to selectively keep the stack wound (both avoiding expensive recomputation and keeping error context), and they separate out multiple error recovery strategies from each other, and allow you to pick which one you want at runtime. The latter fixes a core problem of all other error-handling systems: that the high-level code usually has the context to determine why a low-level operation was happening, and therefore how the error should be recovered from - but it doesn't (or shouldn't) have knowledge of what low-level operations are happening, because that breaks encapsulation. Meanwhile, the low-level code obviously knows about itself - but it doesn't have the high-level context necessary to determine which of several error-recovery strategies to take. A condition system exposes multiple named restarts, which are error-recovery strategies, at the low-level code (technically, you can place them everywhere), and then the higher-level code can choose among those restarts (with names like "abort", "retry", "continue", "ignore") when an error occurs, based on the high-level context and the condition type (because conditions, like exceptions, also have types). Note that these restarts do not necessarily unwind the stack. The stack can remain "wound" to the point of the error, or it can be partially unwound if a restart a few frames up is selected. Exceptions allow none of this - the moment an exception (or a Rust error type) is thrown/returned, the stack is unwound up to the error-handling point, and all context not explicitly encoded is lost (as well as the possibility of retrying an operation (perhaps with different parameters)). Short example: let's say you're writing parser for a log format that goes line-by-line. There's the high-level "read-log-file" function, that calls several other functions that eventually calls a "parse-log-file-line" function. From the local perspective of just that low-level function, if it's fed a line it can't parse, there are multiple valid error-recovery strategies: it could try to repair the line as best as it could, it could add the corrupted line to a list, it could skip the line, or it could explode and die. With a normal exception system, you have to either thread a dedicated argument specifying the strategy down to that function, hard-code it to pick a particular strategy, or re-write that function for your particular application. With conditions, the parse-log-file-line function could throw a condition INVALID-LINE and expose each of the previous options as restarts, and the high-level code could pick one of them, or even handle the condition itself! Longer examples: https://lisper.in/restarts https://lisper.in/restarts https://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html https://gigamonkeys.com/book/beyond-exception-handling-condi...
- zozbot234 5y agoThe Common Lisp condition system is just an implementation of composable effects, which are useful for plenty of other stuff beyond error handling.
- fouric 5y agoIt sure would be nice if we could even "just" get them for error-handling... I didn't know that that's what their real name was. Is there a place that I can read an easy-to-understand explanation of them without a lot of math?
- FridgeSeal 5y agoNot identical, but very related, this is an article on algebraic effects that’s really straightforward and well-written: https://overreacted.io/algebraic-effects-for-the-rest-of-us/ https://overreacted.io/algebraic-effects-for-the-rest-of-us/
- zvrba 5y agoI like the article, though... the problems it illustrates with effects are usually solved by DI/IoC containers. It even says so: > Effect handlers let us decouple the program logic from its concrete effect implementations without too much ceremony or boilerplate code. For example, we could completely override the behavior in tests to use a fake filesystem [..] Yes, I understand the fundamental difference between effects and DI (effects suspend the execution and search the call stack for the handler - like exceptions, but without immediate unwinding). Maybe we could solve the problem with code being able to throw arbitrary exceptions by injecting an "Error" interface as well and then using some discipline? (I.e., instead of throwing stuff directly, we invoke methods on the Error interface.) Actually, I have to think more about this. Thanks for the link, it gave me some food for thought.
- quotemstr 5y agoCommon Lisp, as another commentator described in detail below. Common Lisp is a state of grace from which we have shamefully fallen. It's old out here, east of Eden.
- nemothekid 5y agoCalling exceptions "Go envy" or a "mistake" seems like naive criticism. Ignoring the fact that the Rust is as old as Go; there is a lot of time spent in building language features with an, from my point of view, academic level of rigor. There have been a lot of RFCs discussing exceptions in Rust[1][2][3], and the tradeoffs and design have been discussed thoroughly. If you have a design in mind that is safe, robust and performant you are welcome to submit one - as my understanding is the Rust team does not have an ideological slant against exceptions. I've never understood the hard-on by some C++ fanboys to rush exceptions. You can build (IMO) more reasonable code without abusing ~~gotos~~ exceptions, and a bad exception design (like Java's) is strictly worse than even Go's error handling. [1] https://github.com/rust-lang/rfcs/pull/243 https://github.com/rust-lang/rfcs/pull/243 [2] https://github.com/glaebhoerl/rust-notes/blob/268266e8fbbbfd91098d3bea784098e918b42322/my_rfcs/Exceptions.txt https://github.com/glaebhoerl/rust-notes/blob/268266e8fbbbfd... [3] https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md https://github.com/rust-lang/rfcs/blob/master/text/0243-trai...
- kevinmgranger 5y agoBesides, Result types with sufficiently advanced ergonomics might as well be semantically identical to exceptions.
- pjmlp 5y agoThe so called "Java design" as urban myth, traces back to CLU, Mesa/Cedar, Modula-2+, Modula-3, and C++. Naturally the Java designers thought to adopt a feature that was increasingly being made available. And they only got out of C++ due to the language dialects out there regarding RTTI and exceptions.
- ganbatekudasai 5y agoSum and product types are easier to reason about in total than exceptions. For errors they just integrate into the return type and can be handled as monads. They don’t require additional syntax and can directly be passed into other functions. They have existed since (at least) the early 70s.
- xondono 5y ago> they made a mistake by leaving out exceptions > Just like the "type after identifier" thing > ossifying those fashion trends into immutable mediocrity. Can you explain what (in your opinion) was lost here? Because I don’t see how this is some kind of critical issue, it just looks like some minor semantics that you just don’t like. I don’t see what type of “cost” was payed here that you think implies mediocrity. Maybe I’m biased because I come from firmware development, but I’d rather have error handling be explicit and as close as possible to the error source.
- greydius 5y agoCitations needed
- weavie 5y agoWith exceptions you still need to decide what you are going to throw. I'm not really seeing how this is a hack that using exceptions would avoid?
- onei 5y agoFor the type after identifier, the converse is what you see in C family languages. For example: // in C int x = ... // in Rust let x: i32 = ... There's a couple of things that Rust can do better than C here. First, is that Rust is easier to parse because you know that let begins a variable declaration. If we look at C, maybe it's a variable, maybe it's a function and you have to get past the identifier to figure that out. The second more user-facing change is that you can omit the type in Rust if you can derive it from somewhere else, e.g. the return type of a function call or a primitive.