Checked Exceptions
Introduction
Scala enables checked exceptions through a language import. Here is an example, taken from the safer exceptions page, and also described in a paper presented at the 2021 Scala Symposium.
import language.experimental.saferExceptions
class LimitExceeded extends Exception
val limit = 10e+10
def f(x: Double): Double throws LimitExceeded =
  if x < limit then x * x else throw LimitExceeded()
The new throws clause expands into an implicit parameter that provides a CanThrow capability. Hence, function f could equivalently be written like this:
def f(x: Double)(using CanThrow[LimitExceeded]): Double = ...
If the implicit parameter is missing, an error is reported. For instance, the function definition
def g(x: Double): Double =
  if x < limit then x * x else throw LimitExceeded()
is rejected with this error message:
  |  if x < limit then x * x else throw LimitExceeded()
  |                               ^^^^^^^^^^^^^^^^^^^^^
  |The capability to throw exception LimitExceeded is missing.
  |The capability can be provided by one of the following:
  | - Adding a using clause `(using CanThrow[LimitExceeded])` to the definition of the enclosing method
  | - Adding `throws LimitExceeded` clause after the result type of the enclosing method
  | - Wrapping this piece of code with a `try` block that catches LimitExceeded
CanThrow capabilities are required by throw expressions and are created by try expressions. For instance, the expression
try xs.map(f).sum
catch case ex: LimitExceeded => -1
would be expanded by the compiler to something like the following:
try
  erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue
  xs.map(f).sum
catch case ex: LimitExceeded => -1
(The ctl capability is only used for type checking but need not show up in the generated code, so it can be declared as erased.)
As with other capability based schemes, one needs to guard against capabilities that are captured in results. For instance, here is a problematic use case:
def escaped(xs: Double*): (() => Double) throws LimitExceeded =
  try () => xs.map(f).sum
  catch case ex: LimitExceeded => () => -1
val crasher = escaped(1, 2, 10e+11)
crasher()
This code needs to be rejected since otherwise the call to crasher() would cause an unhandled LimitExceeded exception to be thrown.
Under the language import language.experimental.captureChecking, the code is indeed rejected
To integrate exception and capture checking, only two changes are needed:
- CanThrowis declared as a class extending- Control, so all references to- CanThrowinstances are tracked.
- Escape checking is extended to tryexpressions. The result type of atryis not allowed to capture capabilities defined in the body of thetry.