Control flow and expressions
Z keeps familiar if, while, and for...of shapes while making the difference
between statement control flow and value-producing control flow explicit.
Conditions always require boolean:
if (count > 0) { process(count);}Integers, strings, options, and native nullable handles do not participate in truthiness.
Statement branches
Section titled “Statement branches”An if branch may use braces or contain one unbraced statement:
if (value < 0) return -1;if (value == 0) return 0;return 1;else binds to the nearest unmatched if, following the familiar
TypeScript/C-family rule.
Use a ternary for a concise two-way value:
const label = ready ? "Ready" : "Waiting";if as an expression
Section titled “if as an expression”A larger conditional can produce a value. Use select to supply the value of
the local expression block:
const description = if (value == 10) { const label = "ten"; select label;} else if (value > 10) { select "large";} else { select "small";};Every reachable branch must select one compatible type. Blocks do not return their last expression implicitly.
select, return, and throw have deliberately different destinations:
| Keyword | Destination |
|---|---|
select value |
The nearest local value-producing block |
return value |
The enclosing function or closure invocation |
throw error |
The enclosing typed error channel |
That distinction remains stable even when control flow is nested.
Statement loops
Section titled “Statement loops”Z uses while and for...of:
let total = 0;let index: usize = 0;
while (index < values.length) { total = total + values[index]; index = index + 1;}
for (const value of values) { if (value < 0) continue; if (value == 0) break; inspect(value);}continue begins the next iteration. break exits a statement loop. Z does
not add JavaScript’s for...in; collections expose values or typed entries
through for...of instead of iterating string property names.
Loops as expressions
Section titled “Loops as expressions”while and for...of can also produce values. Their required else block
handles natural exhaustion, including a loop that executes zero times:
const firstEven: Option<i32> = for (const value of values) { if (value % 2 == 0) { select Option.some(value); }} else { select Option.none;};let attempts = 0;const status = while (attempts < 3) { attempts = attempts + 1; if (ready()) { select "ready"; }} else { select "exhausted";};A plain break is invalid inside a value-producing loop because it would leave
the expression without a value. Use select to finish that loop with a value;
return and throw can still leave the enclosing function.
The first executable value-producing loop tier is synchronous. Check the implementation status before composing it with advanced async control flow.
Pattern matching
Section titled “Pattern matching”match is Z’s exhaustive branch over nominal enum variants, Option<T>, and
Result<T, E>. It can be a statement or an expression and follows the same
select rule for multi-statement arms.
See Enums and matching for construction, payload binding, wildcard arms, and borrowed matches.
Runtime nominal checks
Section titled “Runtime nominal checks”Imported Objective-C identity supports a bounded instanceof check:
const body = message.body;if (body instanceof Foundation.NSString) { const text: String = body; consume(text);}The true branch narrows the binding. This is currently an Objective-C interop operation, not general runtime reflection over arbitrary Z types.
A negated early-return guard carries that proof forward as well:
if (!(body instanceof Foundation.NSString)) return;const text: String = body;When both branches continue, only proofs shared by both remain valid afterward.
Runnable examples: conditional expressions and loop expressions.
Next: Collections and views.