Language Policy

Null, Option, Result & Faults

Canonical grammar, semantics, constraints, and deferred boundaries for Null, Option, Result & Faults.

Topaz separates absence, recoverable failure, and aborting faults so a program and every backend agree on which control path is available.

Policy

Option<T> is the canonical absence container; T | null is reserved for nullable data boundaries. ?? unwraps exactly one Option or nullable layer and evaluates its fallback only when empty. ?. evaluates the receiver once, short-circuits arguments, preserves Option versus null, and flattens one member optional layer. Statement-only ??= assigns only when empty and does not implicitly wrap Some. Recoverable failure uses Result<T,E>, Ok, Err, and postfix ?.

Specified behavior

expr? unwraps Ok or immediately returns the same Err from a compatible enclosing function or closure. A fault is not a value: bounds failure, integer zero division, dynamic zero range step, overflow, negative integer exponent, or runtime match miss aborts evaluation. ?, ??, optional chaining, and concurrent cannot catch or convert a fault. Non-faulting collection reads return Option.

Constraints and loud decline

?. is invalid on an ordinary non-null record. ??= on a nonoptional target, cached ??= value where cached: Option<T> and value: T, ? outside a compatible Result-returning callable, nested accidental Err(Err(...)), try syntax, panic keywords, and ordinary public assert are rejected. Mixed Option/null unions are noncanonical until explicitly matched.

Deferred boundary

Fault catching, exceptions, general try, stack recovery, cancellation conversion, automatic retry, and broader assertion frameworks are deferred. Runtime faults may have host cleanup policy, but the language does not promise that they become recoverable Result values or drain every lexical defer.

Worked example

TOPAZ
function parsePort(text: string) -> Result<int, string> {
    match toInt(text) {
        case Some(port) => Ok(port)
        case None => Err("port must be an integer")
    }
}

function start(text: string) -> Result<string, string> {
    let port = parsePort(text)?
    return Ok("listening on {port}")
}