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
| Situation | Use |
|---|---|
| No value is an ordinary outcome inside Topaz | Option<T> with Some(value) or None |
| External data explicitly carries a nullable field | T | null |
| The caller can respond to a meaningful error | Result<T, E> with Ok(value) or Err(error) |
| The program violated a runtime contract | A 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:
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:
topaz check failures-resources.tpz
topaz run failures-resources.tpz
input: 25
ready: Build application, 25 minutes
inspection finished
input: later
cannot add task: minutes must be a positive integer
inspection finishedtoInt 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?.namehas anOptionresult, andmaybeUser?.name ?? "guest"produces astring. - For
configPath: string | null,configPath ?? "default.toml"uses the fallback only when the value isnull. target ??= valueis a statement that assigns only when the target is empty. AnOption<int>target needsSome(42), not bare42; Topaz does not insertSomeautomatically.
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:
let tasks = ["Run first program", "Build application"]
print(tasks[2])topaz run intentional-fault.tpz exits nonzero:
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 selectedtest-profilecode; 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 recoverableResult.
Continue with Failures and Resources for the guided exercise, Defer & Resources for cleanup timing, and Runtime Behavior for the runtime fault boundary.