Program Structure

Null, Option, Result & Faults

Select the appropriate representation for standard absence, nullable data, recoverable failures, and runtime contract faults.

Use this guide when a value may be absent or an operation may fail. The primary question is not “which operator is shortest?” but “can the caller continue execution?”

Choose by meaning

SituationUse
No value is an ordinary outcome inside TopazOption<T> with Some(value) or None
External data explicitly carries a nullable fieldT | null
The caller can respond to a meaningful errorResult<T, E> with Ok(value) or Err(error)
The program violated a runtime contractA fault; evaluation aborts instead of producing a value

Prefer Option for standard absence in Topaz APIs. Reserve null for data and interop boundaries that explicitly contain it. Use Result when a failure requires diagnostic details or a recovery decision.

Turn bad input into a recoverable result

This runnable example converts the absence returned by toInt into a descriptive Result:

TOPAZ
record StudyTask {
    title: string,
    minutes: int,
}

function parseMinutes(text: string) -> Result<int, string> {
    match toInt(text) {
        case Some(value) if value > 0 => Ok(value)
        case _ => Err("minutes must be a positive integer")
    }
}

function makeTask(title: string, minuteText: string) -> Result<StudyTask, string> {
    let minutes = parseMinutes(minuteText)?
    Ok(StudyTask { title: title, minutes: minutes })
}

function inspect(minuteText: string) -> () {
    print("input: {minuteText}")
    defer { print("inspection finished") }
    match makeTask("Build application", minuteText) {
        case Ok(task) => print("ready: {task.title}, {task.minutes} minutes")
        case Err(message) => print("cannot add task: {message}")
    }
}

inspect("25")
inspect("later")

Check and run the program:

BASH
topaz check failures-resources.tpz
topaz run failures-resources.tpz
Output
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finished

toInt returns Option<int> because non-integer text yields no parsed value. parseMinutes determines that this absence is significant to its caller and converts it into Err. In makeTask, the postfix ? operator unwraps Ok. Upon encountering Err, it propagates that error immediately from the returning Result function. The final match expression handles both potential outcomes.

Work with optional and nullable values

Optional access and fallback operators maintain this distinction:

  • For maybeUser: Option<{ name: string }>, maybeUser?.name evaluates to an Option result, and maybeUser?.name ?? "guest" evaluates to a string.
  • For configPath: string | null, configPath ?? "default.toml" evaluates the fallback only when the value is null.
  • target ??= value is a statement that performs assignment only when the target is empty. An Option<int> target requires Some(42) rather than a bare 42. Topaz does not wrap values in Some automatically.

Both ?. and ?? evaluate their left operand once. ?. also skips evaluating method arguments when the receiver is empty. Each operator unwraps or flattens a single optional layer without converting Option into null.

Common correction: Use standard . property access for non-optional and non-nullable values. If a type combines Option<T> and null, handle cases explicitly with pattern matching rather than chaining optional operators.

A fault is not an error value

This example requests an out-of-bounds array element:

TOPAZ
let tasks = ["Run first program", "Build application"]
print(tasks[2])

Running topaz run intentional-fault.tpz exits with a non-zero status:

Output
error[TPZ4001]: index 2 is out of bounds for an array of length 2
 --> intentional-fault.tpz:2:7
  |
2 | print(tasks[2])
  |       ^^^^^^^^

The bounds violation constitutes a fault. It halts evaluation immediately and is neither None nor Err. Postfix ?, ??, optional chaining, and concurrent cannot catch or convert faults. Constant expressions containing invalid arithmetic may instead be rejected during static checking prior to execution.

Exact boundaries

  • Postfix ? is valid only within functions or closures whose return type can represent the corresponding error value.
  • Traditional try blocks, exception handling, fault catching, panic keywords, and automatic retry mechanisms are not supported in Topaz.
  • assert(...) is available only in explicitly selected test-profile builds. It is not an assertion mechanism for general application logic.
  • Resource cleanup on normal scope exit should use defer. Fault cleanup operates within a narrower runtime scope, so faults must not be treated as recoverable Result values.

Continue with Failures and Resources for the guided exercise, Defer & Resources for cleanup execution timing, and Runtime Behavior for the runtime fault boundary model.