3 ms·
The workflow you're describing is also how it works in languages without resumable exceptions, except that you're forced to acknowledge when a function call is
by kibwen 2y ago
The workflow you're describing is also how it works in languages without resumable exceptions, except that you're forced to acknowledge when a function call is capable of producing an error. Whether you want to ignore the error, handle the error, or propagate the error, it's all the same; you're just required to be explicit about which approach you're taking (as opposed to exceptions, where the the implicit default is to propagate).
- vacuity 2y agoIndeed. While it is painful for the people who know they have a simpler architecture, making errors and other cross-cutting effects explicit is necessary at some point. It's essential complexity that shouldn't be hidden; it should be addressed from the get-go. Although the industry largely has the wrong incentives and discourages robust, comprehensible programs.
- elcritch 2y agoMy belief is that errors should be handled using effect systems. So in the signature but not muddying the actual return types. Useful effect systems allow the end user to decide where and when to have the compiler enforce errors are handled. Nim has had an effect system for a while but became much more useful when `forbids: [IOError]` was added. It makes it easy to ensure certain type of errors are handled at specific points. More languages should embrace effect systems. Ocaml's is even used to implement multithreading support, albeit effect systems vary widely in design and theory.
- consteval 2y agoI mean this is how a lot of exceptions are handled, even in C++. You can use noexcept and whatnot and you don't have to change types and propagate them out. In Rust, you do. Java has maybe the strongest system because not only do you have to declare what can throw but it checks it at compile-time. That's, to me, a full featured effect system. But errors-as-values are all the rage today. But modifying types, especially every type in the chain, is annoying and overly manual IMO.
- elcritch 2y agoYeah, Java had some good ideas but just not quite there on the UX, like many things with Java sigh. Checked exceptions were annoying because you had to manually annotate the exceptions all the way up your chain. The list of effects should be generated by the compiler, and the IDE should show them when desired. Maybe manually annotated at external API boundaries which double as forcing the API dev to handle unlisted exceptions.
- stackghost 2y ago>you're just required to be explicit about which approach you're taking Yes and I'm opposed to such "if err != nil" boilerplate.
- worik 2y ago> Yes and I'm opposed to such "if err != nil" boilerplate. That is not what Rust boilerplate looks like.
- stackghost 2y agoI'm aware. One of the criticisms often leveled against Go is that it's needlessly verbose when handling errors, which is why I chose that example.
- 6equj5 2y agoAnd you're not opposed to try-catch boilerplate?
- stackghost 2y agoBoilerplate is code you have to write almost as a pro forma thing. If (in go lang, to continue my example) you're just going to keep copy pasting the same if statement to return `err` up to some higher caller, then why write all those lines when at the top level a single try/catch can remove potentially dozens of lines of code?
- soulbadguy 2y ago> except that you're forced to acknowledge when a function call is capable of producing an error Acknowledging the error is handling the error even if partially. The point of exceptions is to only acknowledge error that one can/knows how to properly handle
- kibwen 2y ago> The point of exceptions is to only acknowledge error that one can/knows how to properly handle In Rust this takes a single character. There's effectively no cost to having the programmer acknowledge the error, and there's a large benefit in that you now know that there's no such thing as an error that the programmer ought to have handled but was simply unaware of. That's a huge benefit for writing resilient software.
- elcritch 2y agoIt's one character in the best case. In the worse case you need to convert the error types to your error type which just re-wraps or replicates the upstream error type. Then repeat this for every library type you use. Zigs error design seems saner in this aspect at least. Rust, IMHO, just makes errors require lots of unnecessary manual labor instead of being smart about it. Alternatively everything just gets put into a `dyn trait` and you're effectively just bubbling up errors just like with exceptions, but with way more programmer overhead. The performance overhead of constantly doing if/else branches for errors adds up as well in some situations. Of course a fair bit of Rust code just uses `unwrap` to deal with inconvenient errors.
- binary132 2y agoOne thing I’ve wondered about is, isn’t the cost of checking for the failure case in the good case all the time actually worse (even if only slightly) than the cost of not throwing, which is nothing?
- vacuity 2y agoThere's indeed a predictably present cost for checking for failure all the time. Exceptions, depending on the implementation, often do come with runtime overhead too. If the determining factor is a slight performance gain of exceptions over ubiquitous checking, that would be an exceptional (ha) case. I daresay there are almost always other more salient factors, if harder to rearchitect around.
- binary132 2y agoAIUI most implementations of exceptions only carry a cost on unwind, but I'm not the expert here.
- otabdeveloper4 2y ago> you're forced to acknowledge when a function call is capable of producing an error All functions are capable of producing an unbounded set of errors. (Yes, programming is hard.)