Follow-up to #8: recoverable failures as try/else values + top-level recover (needs #1) #46

Closed
opened 2026-08-31 13:34:51 +02:00 by orkun · 1 comment
Owner

Follow-up to #8, which shipped the panic half in c7c8e27: panic(msg) and assert(cond, msg) abort loud and located (file:line: …) instead of crashing.

This tracks the other half of the RFC split — recoverable failures as values, plus a top-level recover.

Scope:

  • try EXPR else { … } sugar: a fallible call returns a value carrying either an ok payload or an err; the happy path stays short and the else block supplies a fallback. No hidden control flow, no unwinding.
  • A canonical err/result representation — this is the hard dependency on #1 (tagged unions / any): without a uniform tagged value there is no general failure signal to branch on. Sequence after #1, exactly as #8 noted.
  • Optional: a top-level recover so a dev game loop can survive one bad frame (needs a frame-abort/unwind mechanism; keep it dev-only and deterministic).
  • Docs + example (loading a possibly-missing save with a fallback).

Related: #8 (the shipped panic half), #1 (tagged unions — prerequisite).

Follow-up to #8, which shipped the panic half in c7c8e27: `panic(msg)` and `assert(cond, msg)` abort loud and located (`file:line: …`) instead of crashing. This tracks the other half of the RFC split — **recoverable failures as values**, plus a top-level `recover`. Scope: - [ ] `try EXPR else { … }` sugar: a fallible call returns a value carrying either an `ok` payload or an `err`; the happy path stays short and the `else` block supplies a fallback. No hidden control flow, no unwinding. - [ ] A canonical `err`/result representation — this is the hard dependency on **#1** (tagged unions / `any`): without a uniform tagged value there is no general failure signal to branch on. Sequence after #1, exactly as #8 noted. - [ ] Optional: a top-level `recover` so a dev game loop can survive one bad frame (needs a frame-abort/unwind mechanism; keep it dev-only and deterministic). - [ ] Docs + example (loading a possibly-missing save with a fallback). Related: #8 (the shipped panic half), #1 (tagged unions — prerequisite).
Author
Owner

Shipped the value-based half in 0d1b09e.

try EXPR else { … } — a new expression. EXPR yields a result; on ok the whole try expression is the payload and the else is skipped; on err the else block runs and its trailing expression supplies the fallback. Inside the block the failure message is bound to error, so you can log or branch on it. It lowers to a plain branch on the result's tag — no exceptions, no unwinding, no hidden control flow, exactly as the issue asked.

Canonical result representation — ok(v) wraps a success payload, err(msg) a failure; is_ok(r)/is_err(r) classify without unwrapping. A result is a small tagged value ({ ok, payload, err-message }). Rather than block on the full #1 tagged-union/any work, this ships a focused canonical result now (payloads are any i32-width scalar: int/fixed/bool/entity); the general any generalization stays tracked in #1.

Additive & gated — the feature only activates when ok/err/try appear, so programs that don't use it compile byte-identically (verified on snake/testing/reflect/errors), and x reseed + x bootstrap-cfree reproduce the seed byte-for-byte.

Docs + example — examples/library/recover.ludic (loading a possibly-missing save with a fallback — the issue's motivating example), a try keyword page + ok/err/is_ok/is_err builtin pages, and full vocabulary/inventory/editor-grammar sync. Whole CI set green: x test 71/71, x test-tools 30/30, check-vocabulary/impl/docs.

Deferred (deliberately): the optional top-level frame recover — it needs a frame-abort/unwind mechanism, which is out of scope for the no-unwinding value path and better handled with a dedicated dev-loop guard later. Closing on the core try/else + result deliverable.

Shipped the value-based half in 0d1b09e. **`try EXPR else { … }`** — a new expression. `EXPR` yields a `result`; on `ok` the whole `try` expression *is* the payload and the `else` is skipped; on `err` the `else` block runs and its trailing expression supplies the fallback. Inside the block the failure message is bound to `error`, so you can log or branch on it. It lowers to a **plain branch on the result's tag** — no exceptions, no unwinding, no hidden control flow, exactly as the issue asked. **Canonical result representation** — `ok(v)` wraps a success payload, `err(msg)` a failure; `is_ok(r)`/`is_err(r)` classify without unwrapping. A `result` is a small tagged value (`{ ok, payload, err-message }`). Rather than block on the full #1 tagged-union/`any` work, this ships a focused canonical result now (payloads are any i32-width scalar: int/fixed/bool/entity); the general `any` generalization stays tracked in #1. **Additive & gated** — the feature only activates when `ok`/`err`/`try` appear, so programs that don't use it compile **byte-identically** (verified on snake/testing/reflect/errors), and `x reseed` + `x bootstrap-cfree` reproduce the seed byte-for-byte. **Docs + example** — `examples/library/recover.ludic` (loading a possibly-missing save with a fallback — the issue's motivating example), a `try` keyword page + `ok`/`err`/`is_ok`/`is_err` builtin pages, and full vocabulary/inventory/editor-grammar sync. Whole CI set green: `x test` 71/71, `x test-tools` 30/30, check-vocabulary/impl/docs. **Deferred (deliberately):** the *optional* top-level frame `recover` — it needs a frame-abort/unwind mechanism, which is out of scope for the no-unwinding value path and better handled with a dedicated dev-loop guard later. Closing on the core `try`/`else` + result deliverable.
orkun closed this issue 2026-08-31 14:04:32 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: workshopsoft/ludic#46
No description provided.