A fallible function returns a `result` value, built with ok(payload) on success
or err(message) on failure. The caller recovers a value with `try EXPR else {
… }`: on ok the whole expression is the payload; on err the else block runs —
with the failure message bound to `error` — and its trailing expression supplies
the fallback. It is a plain branch on the result's tag: no exceptions, no hidden
control flow, nothing unwinds. is_ok(r) / is_err(r) classify without unwrapping.
Payloads are any i32-width scalar (int/fixed/bool/entity). The feature is
additive and only kicks in when ok/err/try are used, so untouched programs
compile byte-identically (verified) and the C-free bootstrap fixpoint holds.
Complements panic/assert from #8 (the unrecoverable half). The optional
top-level frame `recover` stays deferred (needs a frame-abort mechanism); the
full tagged-union/any generalization is tracked in #1.
Adds the `try` keyword and ok/err/is_ok/is_err builtins across the compiler,
the vocabulary header, JetBrains + TextMate/VSCode grammars, the docs inventory
and pages, examples/library/recover.ludic, and a regression case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 KiB
| id | name | category | kind | tokens | sig | tip | order |
|---|---|---|---|---|---|---|---|
| kw-try | try | control | keyword | try | try EXPR else { … } | Recover a fallible result as a value, with a fallback — no exceptions, no unwinding. | 12 |
A try EXPR else { … } expression recovers a fallible operation without exceptions or unwinding. EXPR evaluates to a result — a value carrying either a success payload (from ok) or a failure message (from err). When it is ok, the whole try expression is that payload and the else block is skipped. When it is err, the else block runs and its trailing expression supplies the fallback value; inside the block the failure message is bound to error, so you can log or branch on it. It is a plain branch on the result's tag — the happy path stays one line, there is no hidden control flow, and nothing unwinds the stack.
The payload is an i32-width scalar (int, fixed, bool, or entity) — the common case of "a number or nothing". For richer payloads and full pattern-matching over tagged unions, see the type-system work (#1).
program SaveLoad {
# a fallible op returns a result: ok(payload) or err(message)
function load_score(slot: int) -> result {
if slot == 1 { return ok(4200) }
return err("no save in that slot")
}
test "ok yields the payload, err falls back" {
let a = try load_score(1) else { 0 } # 4200
let b = try load_score(9) else { 0 } # 0 (fallback)
expect_eq(a, 4200)
expect_eq(b, 0)
}
test "the fallback can read the failure via `error`" {
var logged = 0
let score = try load_score(3) else {
if error == "no save in that slot" { logged = 1 }
0 # trailing expression = the fallback
}
expect_eq(score, 0)
expect_eq(logged, 1)
}
}