← Back to context

Comment by saurik

5 years ago

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.)