Loops

Loops allow you to execute code repeatedly, either a specific number of times or while a condition remains true. Conjure supports both classic while loops and iterator focused for-in style iteration, with plans to expand loop syntax based on real-world usage patterns.

while

The while loop continues executing its body as long as the given condition evaluates to true. The condition is checked before each iteration, so if it is false initially, the loop body will not execute at all.

var retries = 0

while retries < maxRetries {
    var err = connection.attempt()
    if err == null {
        break
    }
    retries += 1
    time.sleep(backoffMs * retries)
}

If no condition is provided, the loop will run indefinitely until a break statement is encountered.

// Game loop
while {
    input.poll()

    if input.isKeyPressed(Key.Escape) {
        break
    }

    world.update(deltaTime)
    renderer.draw(world)
}

for-in

The for-in loop iterates over ranges, arrays, slices, and other iterable types.

for i in 5 {
    io.println("#{i}")  // prints 0, 1, 2, 3, 4, 5
}

The loop variable i is scoped to the loop body and cannot be accessed outside of it.

Iterating Over Ranges

Conjure provides several range syntaxes for for-in loops.

Single value (inclusive): Iterates from 0 to the given value, inclusive.

for i in 5 {
    io.print("#{i} ")  // prints 0 1 2 3 4 5
}

Single value (exclusive): Use the < prefix for exclusive upper bound.

for i in <5 {
    io.print("#{i} ")  // prints 0 1 2 3 4
}

Bracket syntax (inclusive): Use [start:end] for explicit start and end values.

for i in [5:10] {
    io.print("#{i} ")  // prints 5 6 7 8 9 10
}

Bracket syntax (exclusive): Use < before the end value for exclusive upper bound.

for i in [1:<6] {
    io.print("#{i} ")  // prints 1 2 3 4 5
}

With step: Add a third value for custom increment.

for i in [0:10:2] {
    io.print("#{i} ")  // prints 0 2 4 6 8 10
}

Counting down: Use negative values or negative step for reverse iteration.

for i in -5 {
    io.print("#{i} ")  // prints 0 -1 -2 -3 -4 -5
}

for i in [10:1:-1] {
    io.print("#{i} ")  // prints 10 9 8 7 6 5 4 3 2 1
}

Iterating Over Arrays

Arrays can be iterated directly, giving you each element in sequence. When iterating over an array, the loop variable takes on the type of the array’s elements.

var numbers = [5]i32{10, 20, 30, 40, 50}

for num in numbers {
    io.println("#{num}")
}

To access both the index and value during iteration, specify two loop variables separated by a comma. The first variable receives the index, and the second receives the value. This works with any iterable type.

var colors = [3]string{"red", "green", "blue"}

for i, color in colors {
    io.println("Color #{i}: #{color}")
}

Ignoring Values Proposed

If you don’t need the index or the value, use an underscore to ignore it.

// Ignore the index, only use the value
for _, item in items {
    process(item)
}

// Ignore the value, only use the index
for i, _ in items {
    io.println("Processing item #{i}")
}

// Just iterate a specific number of times
for _ in <10 {
    performAction()
}

Iterating Over Custom Types Proposed

Custom types can implement iterator interfaces to work with for-in loops. The standard library provides iterator support for common collections like lists, maps, and sets. See the conjure/overloads module for details.

var map = HashMap[string, i32]{}
map.set("a", 1)
map.set("b", 2)

for key, value in map {
    io.println("#{key}: #{value}")
}

Breaking Out of Loops

The break statement immediately exits the loop. When loops are nested, break only exits the innermost loop.

var found *Entity = null

for entity in world.entities {
    if entity.id == targetId {
        found = entity
        break
    }
}

Continuing to Next Iteration

The continue statement skips the rest of the current iteration and moves to the next one.

for record in records {
    if record.isCorrupted() {
        io.println("Skipping corrupted record #{record.id}")
        continue
    }
    database.insert(record)
}

Labeled Breaks and Continues Proposed

When working with nested loops, you can label loops and break or continue to specific ones. This is particularly useful for complex nested iteration where you need to exit multiple levels at once.

For developers looking to avoid the usage of goto and maintain readable code with nested loops that obey expected control flow semantics (including defer), Conjure supports labeled breaks and continues.

var cell *Cell = null

outer: for row in grid.rows {
    for c in row.cells {
        if c.value == searchValue {
            cell = c
            break outer  // exit both loops once found
        }
    }
}

Loop Inlining Proposed

Conjure plans to support compile-time loop inlining for any iterable type. This allows loops over compile-time known ranges to be completely unrolled. This is powerful for performance-critical code where you want zero loop overhead.

import "@debug"

func main() {
	#for i in 5 {
		io.println(i) // remains a runtime log
	}
}

// Expands to:
io.println(0)
io.println(1)
io.println(2)
io.println(3)
io.println(4)
io.println(5)