Skip to content

Array ranges and counted loops

Implemented with fixed arrays. for, loop, and range are reserved keywords.

[3]i32 values = [10, 20, 30];
for index := range values {}
for (index, value) := range values {}
for (u32 index, i32 value) = range values {}
for (_, value) := range values {}

One binding receives the index. Two bindings receive index, then element value, following Go’s range binding order. Inferred declarations use :=; explicit declarations use = and specify both types in the two-binding form. T in a syntax description means the actual array element type, such as i32; it does not introduce generic type syntax. These forms declare fresh loop-local bindings; = does not reassign existing variables. _ discards either binding. Explicit discarded types are still checked.

The inferred index type is usize. Explicit indices may use any integer type that represents every index from zero through N - 1. For example, u8 can index a 256-element range but not a 257-element range. The internal cursor stays usize, so incrementing after index 255 does not wrap the loop. Explicit element types must accept the array’s element type under ordinary assignment rules; there is no implicit numeric conversion.

The array expression evaluates once before the loop, and its value is copied into private storage. This also happens for empty arrays and index-only ranges; Flecha does not adopt Go’s exception that can omit evaluating a range expression when only constant length information is needed. Iteration proceeds in ascending index order. Each iteration declares fresh index/value bindings. Assigning these bindings does not affect the array or the iteration cursor. Changes to the original array do not change the snapshot. Nested array elements also copy by value; pointer elements copy addresses without owning the pointees.

Fixed-array and slice values are supported. Use range *arrayReference to copy through a reference. Slice ranges take a shared reborrow for the duration of the loop, without copying the backing elements; see checked_slices.md. Slice indices must use usize, including explicit declarations. Strings, numeric ranges, and implicit iteration through a pointer/reference are not introduced here. Existing initialization and borrow checks apply to the source read. References to an iteration binding cannot escape that iteration’s scope.

loop usize i = 0; i < values.len(); i++ {
if i == 1 { proceed; }
values[i]++;
}

The initializer executes once; the condition is tested before every iteration; the update executes after the body. proceed executes the update before testing the condition. break exits without updating. Nested loop control applies to the innermost loop. return, positions, and jumpto retain their existing scope, cleanup, and initialization rules.

The initializer can be a declaration or expression. Its declarations are visible in the condition, update, and body, and end at the end of the loop. Body locals are unavailable in the update. The condition must have type bool. Any clause may be omitted, but both semicolons remain; an omitted condition means true:

loop ;; { break; }

Array indexing uses usize, starts at zero, and requires i < values.len(). An inclusive comparison such as x <= 10 remains useful for numeric counters. The new loop forms do not have trailing also clauses; existing while/also syntax is unchanged.

place++ requires an initialized, writable integer location. It evaluates the location once, adds one, stores the result, and yields the previous value. Variables, array elements, and dereferenced references/pointers are supported. Const, borrow, bounds, and unsafe-context checks apply. Raw-pointer arithmetic, prefix ++, and decrement are not included. Integer overflow wraps at the integer width, matching the current integer addition implementation.

old := values[next_index()]++; // one index call; checked before load/store
(*reference)++;
unsafe { (*pointer)++; }

The parser normalizes counted loops into a scoped initializer and a while node with a separate optional update. Range nodes retain their source and binding metadata for type/capacity diagnostics while owning a normalized snapshot, cursor, and loop. Internal identifiers contain $, which cannot occur in source identifiers. Generated range element accesses are guarded by their loop condition, including the unreachable body of a zero-length range; user indexing keeps its ordinary static checks. Lowered element accesses still use runtime bounds checks.

The shared control-flow graph routes proceed through the update and keeps iteration locals in their own scope. Native tests cover one-time evaluation, snapshot copies, empty/nested ranges, narrow indices, increment target evaluation, nested loop control, returns/jumps, and owning-string cleanup on each exit.