At first glance, exception handling in Plinth looks similar to exception handling in several other languages: it has try...catch...finally and throw, and thrown exceptions can be declared on methods. However, the similarities with other languages do break down in some notable places.
Checked exceptions are a language feature that stop a method from doing something that could throw an exception without also handling that exception somehow. If a method does something that could throw an exception (e.g. calling another method, or running a throw statement), then it must either catch it with a catch block or declare that the method throws it in the method’s throws clause. Unchecked exceptions are exceptions that do not have to be handled in this way.
In some languages, whether an exception is checked or unchecked is a property of the exception itself. For example in Java, all exceptions are checked unless they extend Error or RuntimeException.
In Plinth, all exceptions are checked. However, a method can declare a thrown exception as unchecked, which means that the callers of that method do not have to explicitly handle it. Here’s an example:
void foo(boolean error) throws unchecked Exception, Error {
// foo() can throw an Exception as unchecked, or an Error as checked.
if error {
throw new Error();
} else {
throw new Exception();
}
}
void bar() throws Error {
// bar() must handle Error, as it is thrown as checked from foo().
foo();
}
void baz() throws Error {
if error {
throw new Error();
} else {
// Compilation error: method cannot throw something without declaring it.
throw new Exception();
}
}
Because Plinth separates the concepts of inheritance and exception checking, it does not encounter the kind of problem where temporarily wrapping a checked exception in an unchecked exception is a good solution for getting around API deficiencies. Instead, an exception can be temporarily thrown as unchecked before becoming checked again later on.
In Plinth, Throwable is the top level type in the exception hierarchy, which all exceptions must inherit from in order for throw, catch, and throws clauses to apply to them. Throwable is an interface, meaning it can be easily added to an existing type hierarchy.
Underneath Throwable, there are two main classes of exceptions: Exception and Error. From the language’s point of view, there is no difference between these classes, they are just implementations of Throwable. However, there is a convention about whether an Exception or an Error should be used in any given situation:
For example, if a file cannot be opened an Exception should be used, whereas if a list index is out of bounds an Error should be used.
Generic exceptions are fully supported in Plinth. When an exception is caught at a catch block, each catch block is checked in turn to see if it matches. These checks are performed in the same way as the instanceof operator, which allows them to use all known run-time type information. Since Plinth does not erase generic types at runtime, they can be caught just as with any other Throwable.
TODO: how it works internally (including: what happens when you return from a try and also from a finally, or when you break/continue through a finally clause)
TODO: also mention Control Flow Checking: details about how variable initialisation is checked in try statements, especially final variables