Control flow

status = if age >= 18 { "adult" } else { "minor" }
unless ready { wait() }

for i in 0..10 { print(i) }
while condition { do_work() }

value = loop {
    n = n + 1
    if n * n > 50 { break n * n }
}

if is an expression

It evaluates to the taken branch, which is why it can be assigned. There is no ternary operator because there is no need for one.

unless

if with the condition negated. An expression like if, with an optional else, yielding null when nothing runs. It reads best as a guard:

unless valid(x) { return err }

Ranges

0..10       # exclusive: 0 to 9
0..=10      # inclusive: 0 to 10

Ranges are lazy, so for i in 0..1000000 does not build a list.

for

for x in items { }
for c in "hello" { }         # characters
for byte in b"\x01\x02" { }   # Ints
for (k, v) in config { }     # map pairs, sorted by key
for (i, x) in enumerate(xs) { }

Destructuring is strict: two or more parenthesized names bind each item's elements, and every item must be a list of exactly that length or the loop errors. Silent partial binding would be guessing. Use _ for positions you do not need:

for (_, v) in config { print(v) }

Map iteration yields [key, value] pairs in sorted key order, so a loop over a map is deterministic.

while and loop

while tests before each iteration. loop runs until break, and evaluates to the break value - which is the clean way to write "repeat until you find something".

for and while evaluate to null.

break and continue

Work in for, while and loop. Using one outside a loop is an error. break v supplies a value to a loop.

Iterating a channel

for job in jobs { handle(job) }

Drains a channel until it is closed. This is the idiomatic consumer, and unlike recv it distinguishes a sent null from end-of-channel.

Budgeting a loop

ECKO_MAX_STEPS sets an opt-in step budget, so an accidental infinite loop or an adversarial input degrades into a catchable error instead of hanging. Unset means unlimited. See Resource limits.