Luce Base
LuceEngineeringLuciaOS

11. Absence, failure, and traps

Base separates three conditions: absence is data, T?; recoverable failure crosses a function boundary as T! with an Error; a trap is a violated invariant and is not catchable. There are no nullable references, no exceptions, and no error hierarchies.

11.1 Optionals#

func find_user(id: UserId) -> User*?

if let user = find_user(id):
    show(user)

let count = parse_i64(text) else 0                       # fallback value
let n = parse_i64(text) else trap("not a number")        # assert with a reason
let w = create_window() else error(gfx.no_window, "no window")   # absence becomes failure

none needs an expected optional type. T promotes to T? where expected; the reverse needs if let, match, or the three-arm else. Optionals are one layer. There is no force-unwrap operator; else trap("reason") is the explicit spelling.

11.2 Fallible functions#

func load_config(path: str) -> Config!:
    let data = try files.read(path)
    return try config.parse(data)

T! means "returns T or an Error". try expression is valid only on a T!: on success it yields T, on failure it returns the same error from the current fallible function. A non-fallible caller must catch. T! is a result effect, not a storable type: it cannot be a parameter, field, or element. T?! is a fallible optional.

Representation: a T! is returned as the value plus a two-word Error and a flag, in registers where the ABI allows. Exported fallible functions use the status form of §17.6.

11.3 Errors#

pub let not_found: ErrorCode = ErrorCode.package(1)

error(files.not_found, "configuration file does not exist")

error(code, message) has type never and is legal only in a fallible function or a catch handler. Error is { code: ErrorCode, message: str }. ErrorCode is a package identity plus a u32, assigned with ErrorCode.package(n) in a top-level constant; codes are unique within a package and never collide across packages. The message is a str view: a literal costs nothing, and a formatted message uses format_into on a buffer that outlives the function, an arena or a caller's buffer, never a local; the escape rule of §6.6 rejects the local case.

11.4 catch and recover#

let text = files.read(path) catch failure:
    if failure.code == files.not_found:
        recover ""
    error(failure.code, f"cannot load settings: {failure.message}")

expression catch name: handles only that expression's failure. The handler must recover value, terminate with error or trap, or return. catch binds more loosely than any operator.

11.5 Traps#

trap(message) stops the program with a diagnostic and a source trace. The compiler inserts traps for: out-of-bounds indexing, checked overflow, division by zero, shift by width, a failed T(x) conversion, else trap, assert, a zero arriving in a bare pointer slot at a C boundary (§17.1), invalid UTF-8 in main's arguments, and stack exhaustion. Traps are never recoverable; defer does not run.

11.6 Assertions#

assert(condition, "message") traps when false. The condition must be side-effect-free. Assertions are never removed by a build profile.

11.7 Out of memory#

Out of memory is a recoverable memory.out_of_memory error from the typed allocation helpers (§12.2), not a fatal termination as in full Luce.

Why. Full Luce makes allocation infallible and out-of-memory fatal, because making every list append fallible would poison the whole API for a condition most hosts cannot recover from anyway. Base is where bounded systems are written: an allocator over a fixed buffer running out is an ordinary condition there, and the caller wrote the allocation call, so it can handle the failure.