Language
Error Handling
When a runtime error would otherwise stop your program, a
try block lets you recover and keep going.
Control moves to a catch block, where you can log the
problem, retry, or fall back to a safe value — and an optional
finally block always runs.
try / catch
Put the risky work inside try. If it fails, the
catch block runs instead — the program never crashes:
try
show 10 / 0
catch
show "Couldn't do the math — using a fallback."
end
Inspecting the error
Write catch error to bind the caught error to a value you
can read. It carries eight read-only fields — code,
name, message, line,
column, source, cause, and
stack:
try
create items = [1, 2, 3]
show item_at(items, 99)
catch error
show "Problem " + error.code + ": " + error.message
show " at line " + text(error.line) + ", column " + text(error.column)
end
| Field | What it holds |
|---|---|
error.code | The diagnostic code, e.g. ZX1004. |
error.name | The error's kind, matched by a filtered catch (e.g. DivideByZero). |
error.message | The plain-English explanation. |
error.line / error.column | Where the failure happened (1-based). |
error.source | The file the failure came from. |
error.cause | The underlying error, when this one wraps another (else nothing). |
error.stack | The call stack at the point of failure. |
The name error is only special right after catch — elsewhere it's an ordinary identifier you can use freely.
Filtering by kind, and finally
A try can list several catch clauses. A filtered
clause names a specific error kind with catch Kind as error;
clauses are tested top to bottom, first match wins, and a bare
catch error at the end is the catch-all. A
finally block runs on every exit — success or failure. Raise
your own error with throw:
try
show 1 / 0
catch DivideByZero as error
show "divide: " + error.message
catch IndexOutOfRange as error
show "index: " + error.message
catch error
show "other recoverable error: " + error.code
finally
show "always runs"
end
Supplying a fallback value
A common pattern is to compute into a variable, falling back when the computation fails:
create ratio = 0
try
ratio = 100 / 0
catch
ratio = 1
end
show "Ratio: " + text(ratio)
What can and can't be recovered
try catches ordinary runtime failures — division by zero, an
out-of-range index, a failed conversion, a database error, and the like.
Two kinds of failure are deliberately uncatchable and
propagate straight past every try:
- Security violations (
ZX1200–ZX1299) — you can't swallow an authorization or trust-boundary error. - The runaway-loop guard (
ZX0713) — a loop that exceeds its limit is stopped for good.
Use try where a failure is a normal outcome you can handle — not to mask a security check or an infinite loop. Those are bugs the language refuses to let you hide.
Related: transactions roll back
The data layer builds on the same idea. A
transaction on DB … catch error … end
commits when its body finishes cleanly and rolls the whole batch
back when a recoverable error occurs — try's
all-or-nothing sibling for persistence.
Next: doing several things at once — Concurrency.