Pre-SIP: Checked Exceptions

As a point of curiosity, have you used Rust’s error handling features?

I find that they make error handling with sum types (Result in Rust, equivalent of Either) so easy that exceptions are almost always the wrong tool for the job.

  1. Exceptions are slow–you shouldn’t use them for error cases that can be handled and which arise more than negligibly often
  2. Exceptions capture stack but not data context throughout the stack, which is usually more important, rendering them unhelpful for debugging in many cases
  3. Exceptions favor long-range transport of error conditions rather than local handling, but in most cases the information needed to resolve or cope with an issue is local.
  4. Even with checked exceptions, the compiler can’t verify that all exceptional cases are handled (especially since some JVM exceptions are considered fatal and should not be caught!) since unchecked exceptions can arise nearly anywhere
  5. Exceptions don’t pass between threads, causing additional headaches any time error handling and multithreading intersect. (Which is approximately always.)

Because exceptions are usually the wrong tool, their footprint in the language should be small. Therefore, checked exceptions should only be considered if the impact on the language is essentially trivial. Unfortunately, this proposal has (1) a new keyword, (2) a new symbol, (3) apparently special rules for functions, (4) a new mechanism for binding a generic type identifier, (5) a suggestion of a new function definition operator, (6) new Enum-like syntax for custom exceptions, (7) a custom flatMap-like functionality, and (8) there are still admittedly four potentially problematic areas.

This is a huge footprint on the language which I don’t think can be justified given that there are much better ways to do most error handling.

In particular, the ? operator in Rust is brilliant (I have a macro that emulates it using return), and using map_err (or in Scala, .left.map) to add contextual information and/or change error types makes intentional propagation of relevant information easy.

Additionally, people who like monads already have toolchains that can deal effectively with error cases in a functional context.

So while I think it’s worthwhile considering whether checked exceptions can be added with barely any additional cognitive overhead on the part of the programmer, this approach of inventing a whole new error handling scheme built off of a usually undesirable mechanism is not advisable.

Possibly an effects system can capture this. Possibly something built off of opaque types can capture this, where opaque type Exceptional[E, A] = A simply marks that a checked exception can occur.

But this proposal is way, way, way too heavyweight, and I would actively avoid most code that used exceptions more heavily, even with these admittedly significant improvements to their usability, as there are better options available.

14 Likes