Skip to content

Ranges and Iteration

Chloë Strainge edited this page Oct 14, 2026 · 1 revision

Ranges and iteration

A range describes an interval. start..end excludes the end; start..=end includes it. A reversed bounded range is empty.

for $day in 2..5 { echo $day }
let $days = Array(2..=4)
echo $days...
2
3
4
2 3 4

Open ranges such as 1.. and ..3 are useful for slices. Iteration or expansion requires both bounds, and range iteration requires Integer bounds. Ranges iterate lazily; converting a bounded range to Array materializes its values.

Iterating a snapshot

for evaluates its source once. Arrays supply elements, hashes supply pairs, and ranges supply integers. The loop advances a private receiver, so the source stays available for another traversal. Native arrays preserve unit items too.

Define an iterator

A custom iterator implements a zero-argument next_item returning T | ():

struct Countdown { pub remaining: Integer }
fn Countdown::next_item(): Integer | ()
{
    if $self.remaining == 0 { return () }
    let $answer = $self.remaining
    $self.remaining = $self.remaining - 1
    $answer
}
let $timer = Countdown(remaining: 3)
for $tick in $timer { echo $tick }
echo $timer.remaining
3
2
1
3

Actual () ends a scripted iterator. False, zero, and empty text are valid items. Wrap a unit payload in a struct or one-item array when implementing a custom iterator that must deliver it. Managed item streams provide a boxing convention for this situation.

A direct $timer.next_item call advances the real receiver. That is different from the private copy used by for. Native arrays and hashes also expose direct next_item; calling it removes their next item.

A fixed-shape return such as [Integer, String] | () tells the checker how a for ($index, $label) in ... pattern can be unpacked. Pattern length is still validated before the body starts.


Related: Control flow · Methods · Item streams

Clone this wiki locally