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:
- An array
T[]: its elements, in index order. - An
Iterator<T>, concrete or held as the trait (it: Iterator<T>), advanced until it stops (§15 Iteration). - A value implementing
Into<Iterator<T>>, converted with no.into()written. - A type:
- a range, an integer type within
i64or aT: Int: its values, the least first (for h in Hour;for b in u8runs 256 turns); - an enum without variant data: its variants, in declaration order (
for d in Weekday); - any other type is an error,
u64andcharincluded.
- a range, an integer type within
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.