Comment by nemothekid

5 years ago

The only downside of error codes via Sum types (Rust) seems to be, according to the article, is performance. It then claims that Checked Exceptions are the solution (at least according to Joe Duffy).

Maybe I'm naive to how exceptions are actually implemented, but it seems to me that both a checked exception and Sum Type would incur the same overhead, a single branch to make sure things haven't exploded.

If you want to treat your error result as a first class value, and transport it around, then your sum type can't use the same implementation as exceptions, which can use data-driven stack unwinding to have zero cost in the success case, the data being generated by the compiler and consumed by a stack unwinder after it has been invoked by an exception raise.

As exceptions are an abstraction you can implement them in many ways; one of those is "the same secondary return code error check as you would do manually", but if you assume "errors are extremely rare" (which I assert is fair: people who disagree generally point to a tiny class of things that I would argue aren't errors in the first place, such as "key not found in map" and "file not found on disk") you can use implementations that have literally "zero cost" for success but instead compile all of the exception unwind logic (catch statements and deconstructors) as continuations of the original functions (causing some modest binary code bloat, though the compiler can sometimes avoid this being noticeable) and then do table lookups (which are slow, but not necessarily ridiculous) to find the right on-error unwind target given an on-success function return pointer.

Essentially, I would argue that error signaling is important enough and common enough that it deserves attention by the compiler in the same sense that many of the other things we provide syntax for (such as traits or inherentence) are things which developers can type naive manual implementations of with basic tools (such as switch statements or dictionaries or dragging around lots of function pointers), but if you can abstract it in a way such that the semantics are available to the compiler you can come up with much better ways to handle the problem (such as vtables or polymorphic dispatch caches) for a given set of tradeoffs (such as low memory usage, low construction cost, consistent latency, etc.). If everyone is implementing the feature themselves manually in the code then you have lost any real ability to make great optimizations.

(Note that you don't necessarily have to have it be syntax to do this: you can also have a language such as Haskell--where notably these Either-style errors are usually cited as being from--where they do it in the language but abstracted everything an additional level higher, letting you define a lot of these flow control concepts in terms of a monad, so then downstream users use "do" notation to feel like custom syntax and the monad's bind operator provides a central chokepoint on what was otherwise a bunch of boilerplate. You sometimes--not always--can then do optimizations across the entire program of that shared abstraction. The way languages like Rust and Go are handling this, without support for monads, simply precludes anything other than attempts at reverse engineering semantics from the code, which is ridiculous.)