Language reference
Conditions, loops and matching
Choose values, iterate over arrays and ranges, and exit loops safely.
Bool has the values true and false. Comparisons return Bool. Use not, and and or for Boolean logic. and and or evaluate only the operands needed to determine the result.
Select a result
fn clampLow(value: int, minimum: int) -> int:
if value < minimum:
minimum
else:
value
fn main() -> int:
clampLow(12, 42) This returns 42. A general if expression needs both branches and a compatible result type. Only the selected branch executes, and bindings introduced in it stay within that branch. A non-final validation guard may omit else when its body ends in an explicit fail.
Combine conditions
fn main() -> int:
value = 42
valid = value >= 0 and value < 100
if valid and not false:
value
else:
0 This also returns 42. Write separate comparisons joined with and. Ungrouped chained comparisons are rejected. Floating-point comparisons follow the numerical rules, including NaN behavior; Boolean arrays and numerical tensors remain distinct types.
Update local state in a loop
Numeric and Bool scalars can be reassigned without a mut declaration. Numeric and Bool array bindings can also be reassigned in CPU functions, including across task waits and expected-error handling. Their lengths may change; their element type stays fixed. A while statement reevaluates its Bool condition before each iteration. It may run zero times. In statement form, supply the function result separately after the loop. To obtain a value from a loop, use the expression form described below.
fn main() -> int:
index = 0
total = 0
while index < 5:
if index < 3:
total = total + index
else:
total = total + 10
index = index + 1
total This returns 23. A statement if may omit else; a value-producing if still needs both branches. Assignment to an existing scalar changes that binding; a new name introduced in a branch or iteration stays in that scope. Reassignment preserves the scalar type. Checked numeric/Bool array element writes are described in the array tutorial. break exits the nearest loop; continue skips the rest of its body and starts the next iteration.
Iterate over an array
for visits array elements in index order. It evaluates the array expression once and keeps that storage alive until iteration finishes. Replacing the original binding does not change which array is visited. Writes to writable elements remain visible to subsequent reads. The iteration binding is local to the body and does not replace an outer binding with the same name.
fn main() -> int:
total = 0
for value in [1, 2, 3, 4]:
if value == 2:
continue
total = total + value
total This returns 8. continue advances to the next element. A bare break would end this statement loop immediately. for accepts CPU arrays, including arrays of Bool, Strings, records and function values. Use an explicit conversion to an array when starting from a numerical tensor.
Iterate over an integer range
Use range(stop), range(start, stop), or range(start, stop, step) as a for iterable. These forms are allocation-free loop syntax; they do not construct an array or a reusable iterator value. Arguments are completed int values, evaluated once from left to right. The default start is zero and the default step is one. The stop is exclusive. A negative step counts downward; a direction that cannot reach the stop produces no iterations. A zero step produces a checked arithmetic fault, including for an otherwise empty range. Advancing beyond the int limits ends the range without wrapping around.
fn main() -> int:
total = 0
for index in range(5, -2, -2):
total = total + index
total This returns 8, from 5 + 3 + 1 - 1. Reassigning index inside the body changes that iteration's local value; it does not change the next range value.
Build an array with yield
A for or while expression containing yield builds an array. Each executed yield appends one value in execution order. You can filter values with if, skip an iteration with continue, or stop with a bare break. Values yielded before continue or break remain in the result. A collecting loop has no else result and does not accept break value.
fn main() -> [int]:
squares = for value in [3, -2, 7, 0]:
if value > 0:
yield value * value
squares This returns [9, 49]. All yielded values must have the same element type. A type annotation on the result supplies the element type for numeric literals. If no yield executes, the result is an empty array of that type. The loop runs eagerly before the assignment completes; yield does not suspend execution or create a lazy generator.
Nested statement loops contribute to the enclosing collecting expression. This lets one traversal produce zero, one or several values per iteration.
fn main() -> [int]:
results = for row in range(4):
for value in range(row):
if value % 2 == 0:
yield row * 10 + value
results This returns [10, 20, 30, 32]. break and continue still target the nearest loop. Assigning a nested loop expression to a separate binding creates a separate result array; its yields do not append to the outer result. A function or callback cannot yield into its caller's loop.
fn main() -> [int]:
index = 0
values = while index < 6:
index = index + 1
if index == 2:
continue
yield index
if index == 4:
break
yield index * 10
values This returns [1, 10, 3, 30, 4]. yield accepts the supported array element types: numeric scalars, Bool, Strings, records and functions. Array and tensor elements are not supported. Values must be completed and on the CPU; await tasks and handle expected errors before yielding their results. Yielding does not settle other tasks created in the iteration. Cancellation, faults and resource exhaustion abort construction without returning a partial array.
The result has fresh array storage. Owned elements retain their ordinary aliasing rules: yielding a record does not duplicate arrays referenced by its fields. Existing read locks and mutation permissions still apply. Use generate for an indexed construction with a known count, map to transform an existing array, or a collecting loop when filtering, nested traversal or early exits make the construction clearer.
Get a value from a loop
For a single result rather than a yielded array, both while and for support an else suite that supplies its result when iteration finishes normally, including when the body never runs. break value ends the nearest loop and supplies its result instead; that path skips else. Every break and the else result must have compatible types. There is no implicit last-iteration result, and continue never supplies a value.
fn main() -> int:
for value in [3, 7, 9]:
if value > 5:
break value
else:
0 This returns 7. The same search over an empty array would return 0. Statement and collecting loops use bare break; a single-result expression loop uses break value.
fn main() -> int:
index = 0
result = while index < 10:
index = index + 1
if index == 4:
break index * 10
else:
7
result + index This returns 44. The else suite runs only after a false condition or exhausted iterable, never after break, a propagated error, a checked fault or cancellation. Loop expressions may return any supported completed value, including arrays, Strings, records, functions and tensors. Existing type, ownership and residency rules still apply.
Exit safely around tasks
break and continue target the nearest enclosing loop in the same function. A closure cannot jump into its caller's loop. Statements after an unconditional loop transfer are rejected as unreachable.
Tasks created in an iteration must settle before every backedge, break and continue. Await every relevant child before the exit; a cancellation request alone does not establish settlement. Exiting a completed nested parallel group closes and releases its scheduler records before control transfers. Existing error and checked-fault precedence applies to that cleanup.
A task started before a loop may still be running afterward: the loop may execute zero times or break before an await. Await it outside the loop before mutating storage it reads. Cancellation and resource limits apply to all loop forms. For long loops that start tasks, use a nested parallel block for each batch so completed groups release scheduler records between iterations.
Match a declared variant
Use match to select an enum or error alternative. Cover every alternative or use a final _ wildcard branch. Names bound to a variant's fields exist only in the selected branch. Match guards and nested destructuring are not supported.