Typed exceptions and finally
Use exceptions for exceptional control flow that must cross several call frames. Use Result<T> for expected operation failure that callers should handle explicitly.
Source
import go errors from "errors";
import go fmt from "fmt";
class NotFoundException extends Exception {
constructor(message: string) { super(message); }
}
class GoneException extends NotFoundException {
constructor(message: string) { super(message); }
}
function lookup(kind: int): bstring {
try {
if (kind == 0) { return "hello"; }
if (kind == 1) { throw new NotFoundException("missing"); }
if (kind == 2) { throw new GoneException("closed"); }
throw errors.New("backend unavailable");
} catch (err: NotFoundException) {
return "not-found:" + err.message;
} catch (err: error) {
return "error:" + err.Error();
} finally {
fmt.Print("clean ");
}
}
function main(): void {
fmt.Println(lookup(0));
fmt.Println(lookup(1));
fmt.Println(lookup(2));
fmt.Println(lookup(3));
}Run
keika check exceptions.km
keika run exceptions.kmExpected output:
clean hello
clean not-found:missing
clean not-found:closed
clean error:backend unavailableGoneException is caught by the earlier NotFoundException clause because it derives from it. The final error clause also accepts the raw Go error created by errors.New. finally executes before every return becomes visible to the caller.
Catch order is checked statically. Putting catch (err: error) before the typed clause is rejected as unreachable; that failure is also part of the documentation test suite.
Rethrow without replacing the value
Inside a catch body, bare throw; preserves the currently caught exception:
try {
readConfiguration();
} catch (err: ConfigException) {
log(err.message);
throw;
}An ordinary Go/runtime panic does not become a catchable exception. It still runs finally, then continues unwinding.
See Errors and nullability for the Result, Go error, exception, and panic boundary.