leviathan-lang.com / docs / learn
Errors and resources
Expected absence as a value, programmer errors as exceptions, and deterministic cleanup with using.
Absence versus errors
Leviathan draws a deliberate line. Expected absence is a value; programmer errors throw. A
parse that might fail returns a T?; an out-of-bounds index throws a catchable
RuntimeException.
Optionals for parsing, exceptions for bugs
int? parsed = "42".toInt(); // strict: None on garbage, never a silent 0
int n = parsed ?? 0;
console.writeln(n);
int bad = "forty-two".toInt() ?? -1;
console.writeln(bad);
// Throwing path: the thrown value must implement IException.
try {
throw RuntimeException("boom");
} catch (IException e) {
console.writeln("caught: ${e.message}");
}
Array<int> xs = [1, 2, 3];
try {
console.writeln(xs.at(10));
} catch (RuntimeException e) {
console.writeln("caught: ${e.message}");
}
42
-1
caught: boom
caught: index 10 out of bounds (length 3)
Deterministic cleanup
There is no finally. Deterministic cleanup is using: it owns a resource that implements
IDisposable and runs close() on every way the block exits: falling off the end, return, a
throw unwinding past it, or a break. Multiple resources close in reverse order.
using closes in reverse order
class Resource : IDisposable {
public string name;
public void close() { console.writeln("closed ${name}"); }
}
void work() {
using Resource a = Resource("first");
using Resource b = Resource("second");
console.writeln("working");
} // b.close() runs, then a.close(), on every exit edge
work();
working
closed second
closed first
The same rule makes files safe. File implements IDisposable, so both files below are closed
however backup exits:
Copying a file
void backup(string path) {
using File src = File(path, std::read);
using File dst = File(path + ".bak", std::write);
dst.writeln(src.readln());
} // dst.close() runs, then src.close(), on every exit edge