Program Structure

Null, Option, Result & Faults

Choose the right representation for ordinary absence, nullable data, recoverable failure, and runtime contract faults.

Use this page when a value may be missing or an operation may fail. The first question is not “which operator is shortest?” but “can the caller continue?”

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 ordinary absence in Topaz APIs. Keep null for data and interop boundaries that genuinely contain it. Use Result when the failure needs a reason or a recovery decision.

Turn bad input into a recoverable result

This complete program 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 it:

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 text that is not an integer has no parsed value. parseMinutes decides that this absence matters to its caller and turns it into Err. In makeTask, postfix ? unwraps Ok; when it sees Err, it returns that error immediately from the compatible Result-returning function. The final match handles both outcomes explicitly.

Work with optional and nullable values

Optional access and fallback preserve the distinction:

  • For maybeUser: Option<{ name: string }>, maybeUser?.name has an Option result, and maybeUser?.name ?? "guest" produces a string.
  • For configPath: string | null, configPath ?? "default.toml" uses the fallback only when the value is null.
  • target ??= value is a statement that assigns only when the target is empty. An Option<int> target needs Some(42), not bare 42; Topaz does not insert Some automatically.

Both ?. and ?? evaluate their left side once. ?. also skips method arguments when the receiver is empty. Each operator unwraps or flattens one optional layer; it does not silently turn Option into null.

Common correction: use ordinary . for a value that is not optional or nullable. If a type mixes Option<T> and null, match the cases explicitly instead of stacking shortcuts.

A fault is not an error value

This program asks for an array element that does not exist:

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

topaz run intentional-fault.tpz exits nonzero:

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 is a fault: it aborts evaluation and is neither None nor Err. Postfix ?, ??, optional chaining, and concurrent do not catch or convert faults. Constant forms of invalid arithmetic may instead be rejected while checking, before the program runs.

Exact boundaries

  • Postfix ? works only inside a function or closure whose return type can carry the same error value.
  • General try, exceptions, fault catching, panic keywords, and automatic retry are not current Topaz forms.
  • assert(...) is available only in explicitly selected test-profile code; it is not an assertion statement for ordinary programs.
  • Cleanup on normal scope exits belongs to defer. Fault cleanup has a narrower runtime boundary, so do not treat a fault as a recoverable Result.

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