Lyric v1.6.0

Errors

Lyric separates two kinds of failure.

Panics

import std.core { panic, assert };

fn take(items: int[], index: int): int {
    if (index < 0 || index >= items.length) {
        panic("index out of range");
    }
    return items[index];
}

fn main(): int {
    assert(true, "this holds");
    return take([1, 2, 3], 1);
}

std.core also offers todo and unreachable for the same purpose.

Exceptions

A function that can throw declares throws. A caller either handles it or declares throws itself.

throws without a type means Throwable — anything. Naming a type narrows the declaration, and a catch for that type then covers the call completely.

import std.core { Exception };

fn parse(text: string): int throws Exception {
    if (text == "") {
        throw Exception { text = "empty input" };
    }
    return 1;
}

fn main(): int {
    try {
        let n = parse("");
        return n;
    } catch (e: Exception) {
        return 0;
    }
}

A catch may bind by type, bind everything, or bind nothing:

import std.core { Exception };

fn risky(): int throws {
    throw Exception { text = "no" };
}

fn main(): int {
    try {
        return risky();
    } catch (e: Exception) {
        return 1;
    } catch (other) {
        return 2;
    }
}

main cannot declare throws; an exception that reaches it aborts the process.

Cleanup

defer runs when the scope ends, on the normal path and while unwinding:

import std.core { Exception };
import std.io.console { println };

fn work(): int throws Exception {
    defer println("released");

    throw Exception { text = "failed" };
}

fn main(): int {
    try {
        return work();
    } catch (e: Exception) {
        return 0;
    }
}

There is no finally; defer covers it.

Custom exception types

Any type that satisfies Throwable can be thrown. Throwable is built in and needs no import; std.core.Exception is the ready-made implementation, whose field is text and whose method is message().

class NotFound :: [Throwable] {
    what: string,
    fn message(): string { return "not found: " + this.what; }
}

fn lookup(key: string): int throws NotFound {
    throw NotFound { what = key };
}

fn main(): int {
    try {
        return lookup("key");
    } catch (e: NotFound) {
        return 0;
    }
}