Control flow
Meta: keep if, case, and for close together — they're all expression-form. The "no statements vs. expressions" point is worth stating once at the top.
Everything is an expression
if,case,for,tryall return values- the value is the last expression in the chosen branch
- so they're assignable, returnable, usable as args (see Blocks)
if / else
pub status = if (active) { "on" } else { "off" }
- condition must be
Boolean!— no truthiness; a non-Booleancondition is a compile error (condition must be Boolean, got Int!) else ifchains:if (a) {..} else if (b) {..} else {..}- no-else form returns
nullwhen the condition is false — result type is nullable (e.g.if (false) { "value" }isnull);else ifchains with no finalelseare likewise nullable - branches must merge to a common type; if they diverge they widen to a union (see Flow-sensitive typing)
- flow-sensitive narrowings from the condition apply per-branch (then=truthy, else=falsy) — see Flow-sensitive typing
- a no-else
ifwhose then-branchreturns does NOT make an enclosing fn non-null (the branch may be skipped → result stays nullable)
case
case (value) {
1 => "one"
2 => "two"
else => "?"
}
- clauses tried top-to-bottom; first match wins (incl. a stray duplicate or an
elseplaced before later clauses) - clause bodies must merge to a common type (
Case.Infer: clause N type mismatchotherwise) - no compile-time exhaustiveness check: if nothing matches and there's no
else, it's a runtime errorno case clause matched the value: <v>
Value patterns
- literal scalars: ints
1, floats3.14, strings"foo", booleanstrue/false,null, enum values - literal strings/values auto-coerce to the operand's scalar/enum type (e.g. operand
URL!/an enum, clause"https://..."/"ACTIVE") — same coercion as field/arg/return boundaries (see Enums and scalars) - coercion only applies to syntactic literals; a non-literal value whose type differs from the operand is a mismatch (
clause N value type mismatch)
Type patterns
case (animal) {
c: Cat => c.purr
d: Dog => d.bark
}
- form is
binding: Type => expr; the binding is the operand narrowed toTypeinside that clause - operand must be a union or interface (see Interfaces and unions); on a plain object it's an error (
type pattern requires a union or interface operand) - the named type must be a member of the union / an implementer of the interface, else
type X is not a member of union Y - an interface itself works as a pattern — matches any implementer (a typed catch-all); place specific types before it
- also used in
catchoverErrorsubtypes (see Errors:try,catch,raise)
Optional operand
case { x > 0 => "+", x < 0 => "-", else => "0" }— omitting the operand desugars tocase (true); clauses areBoolean!conditions
for
for (i < 10) { i += 1 } # loops while condition is Boolean! true
for { ... break ... } # infinite; exit via break/return
- condition must be
Boolean!(same asif) — nofor (x in xs); iterate collections withxs.each { x => ... }(see Collections) - a
foris an expression: yields the last body value, ornullif the body never ran - condition loops are always nullable (the body may be skipped), so a value-
break/returninside the body cannot make the loop result non-null break valueoverrides the yielded value (for { break "loop done" }yields"loop done")
break and continue
- valid only inside a loop or a block-taking call; otherwise compile error (
break outside of loop or block-taking call/continue outside of loop or block arg invocation) breakexits;break valuemakes the loop / block-call yieldvalue; barebreakyieldsnullcontinueskips to the next iteration; in.map,continue valueinsertsvalueinto the result (barecontinueinsertsnull); in.each/forit just advances- target the nearest enclosing loop or block-call only; an ordinary nested function does NOT inherit the enclosing block's break/continue target (compile error if it tries)
return
- exits the enclosing function / method / constructor early; outside one is a compile error (
return outside of function) - value type must satisfy the function's declared return type
- unwinds through enclosing blocks and loops (e.g.
returnfrom inside.eachexits the whole fn; see Blocks) - a
returnin a skippable branch (no-elseif, conditionfor) does not make the fn non-null returnis NOT an error and is NOT catchable bytry/catch(see Errors:try,catch,raise)