Download

7. Control Flow

if / else#

x :: if b > 2 { 2 } else { 3 }

if cond {
    // ...
} else if other {
    // ...
} else {
    // ...
}

if is an expression; used as a value, its branches must give compatible types.

match#

result :: match value {
    42                  -> "literal int"
    is i32 if value > 0 -> "pos"
    is i32              -> "non-pos"
    is string           -> "str: $value"
    'A'                 -> "literal char"
    is char             -> "?"
}

A match must be exhaustive (§4 Exhaustiveness). §4 Pattern matching gives its full grammar.

for (universal loop)#

// Exclusive range
for i in 0..<10 {
    println("$i")
}

// Inclusive range
for i in 0..=10 { ... }

// Iterate over a collection (built-in array, Iterator, or an Into<Iterator> type)
for item in items {
    println(item)
}

// Tuple-destructuring binding: each yielded tuple is unpacked into the names.
for [key, value] in pairs {
    println("${key} = ${value}")
}

// While-like
for cond {
    // ...
}

// Infinite
for {
    if exit_condition { break }
}

// Iter without a binding (sugar for `for _ in <iter>`)
for 0..<GENERATIONS {
    step()
}

Without a variable, for <expr> { body } depends on the type of <expr>. A bool makes it loop while the expression holds; an iterable value (an array, an Iterator<T>, a Range, ...) makes it for _ in <expr>. Any other type is an error.

The variable of a for x in ... is a constant binding, bound anew at each turn, and so is each name of for [k, v] in .... Assigning it is an error; modifying the value it designates, as x.push(y) does, is allowed. The condition form binds nothing: in for i > 0 { i -= 1 }, i is an ordinary local.

What a loop iterates over is evaluated once, before the first element. A range evaluates lo, then hi, when it is built, so for i in 0..<n { n = 5 } runs as many times as n held on entry. A lazy chain evaluates its source, then the argument of each combinator, then that of the terminal, once each, even when the source is empty: xs.map(f()).filter(p()).any_match(q()) evaluates xs, f(), p(), then q().

for x in expr accepts four kinds of expr:

  1. An array T[]: its elements, in index order.
  2. An Iterator<T>, concrete or held as the trait (it: Iterator<T>), advanced until it stops (§15 Iteration).
  3. A value implementing Into<Iterator<T>>, converted with no .into() written.
  4. A type:
    • a range, an integer type within i64 or a T: Int: its values, the least first (for h in Hour; for b in u8 runs 256 turns);
    • an enum without variant data: its variants, in declaration order (for d in Weekday);
    • any other type is an error, u64 and char included.

Targeting an outer loop: break <var> / continue <var>#

A bare break or continue acts on the innermost loop. break <name> and continue <name> act on the nearest enclosing loop whose iteration variable, or label (below), is <name>. The names of a destructuring loop (for [k, v] in ...) are not targets. Any other name is an error, the name of a local included.

for row in grid {
    for cell in row {
        if cell == 0    { continue }      // innermost: bare
        if cell == goal { break row }     // the row-loop, by its variable
    }
}

A loop with no variable to target takes a label, a name: prefix: a condition loop (for <cond>), an infinite loop (for {}), a destructuring loop, or one whose variable is _.

scan: for !done {
    for cell in row {
        if cell == goal { break scan }
    }
}

The compiler warns about a label on a loop that has an iteration variable: target that loop by its variable instead.

defer#

defer registers a statement or a block to run when its block is left, the last registered first.

process :: fn(path: string) -> string | Error {
    file :: open(path)
    if file is Error { return file }
    defer close(file)

    {
        tmp :: allocate_temp()
        defer free(tmp)
        // tmp freed at the end of this sub-block
    }

    // file closed at the end of the function
    return read_all(file)
}

A return, like the trailing value of a block, computes its value before the defers run, so a defer that writes to a local the value reads does not change the value.

A defer body cannot return, yield or await, nor break or continue a loop it is not inside; a return in a lambda written there returns from that lambda. A defer in a generator is an error.

When the program stops (a panic, a failed run-time check, unreachable), the pending defers of every frame run, innermost first, before the process ends. A stop inside a deferred body ends the program at once, without running the defers still pending. There is no recover.

Unreachable code#

The compiler warns on the first dead statement of each block: one that follows, in the same block, a return, a break, a continue, or a for loop that no break leaves.