Control flow
for
The for-loop iterates over each element in the range expression. The
range may be anything that has an iterator() method that
returns an iterator.
void main() {
println("the range can be any collection");
for element in [1, 2, 3] {
println(element);
}
println("or an exclusive numeric range");
for element in 1..3 {
println(element);
}
println("or an inclusive numeric range");
for element in 1...3 {
println(element);
}
}Bind a second loop variable to also get the zero-based iteration index.
Loop index semantics
The index is an int that counts iterations from 0 even
for ranges that do not start at 0, and each iteration gets a fresh copy,
so assigning to it does not affect iteration.
void main() {
for element, index in ["a", "b", "c"] {
print(index);
println(element); // prints 0a 1b 2c
}
}while
The while-loop loops until its condition evaluates to false.
void main() {
int i = 0;
while i < 3 {
println(i); // prints: 0 1 2
i++;
}
}break and continue
break exits the innermost enclosing loop or
switch statement, and continue skips to the
next iteration of the innermost enclosing loop:
void main() {
for i in 0..10 {
if i == 3 {
continue; // skips 3
}
if i == 5 {
break; // stops the loop
}
println(i); // prints: 0 1 2 4
}
}break and continue in switch
Since switch cases don't fall through,
break is only needed in a switch to skip the
rest of the matched case body. A continue inside a
switch applies to the enclosing loop.
if
void main() {
var answer = 42;
if answer == 0 {
println("answer is zero");
} else if answer < 0 {
println("answer is negative");
} else {
println("answer is ", answer);
}
// If-expression, aka conditional expression, aka ternary expression.
println("answer is ", answer == 42 ? "right" : "wrong");
}An arm that diverges (calls a never-returning function
like abort) contributes no value: the ternary takes the
other arm's type.
The is operator tests whether an enum value is a
specific case. Appending a name binds the case's payload in the
then-branch, using the same semantics as switch case
bindings. The binding borrows the payload rather than copying it, so the
payload cannot be moved out of the binding:
enum Outcome {
Ok(int value),
Err(string error),
}
string describe(Outcome& outcome) {
if *outcome is Err e {
return e;
}
if *outcome is Ok {
return "ok";
}
return "unknown";
}
void main() {
var ok = Outcome.Ok(value = 1);
println(describe(ok)); // prints "ok"
}switch
Case bodies don't fall through to the next by default, so you don't
have to manually break after each case. Variables can be
declared directly in a case body without wrapping it in a block.
void main() {
var value = 0;
switch (value) {
case 0:
println("value is zero");
case 1:
println("value is one");
default:
println("value is something else");
}
}Exhaustive-switch safety check
When switching over an enum without a default, the
compiler inserts a safety check that aborts with an error if the value
is somehow not one of the enum's cases. Valid but unhandled cases still
fall through to the code after the switch. The check is emitted in every
build mode except release mode (--release), where the
compiler instead assumes the value is always a valid case.
A switch can also be used as an expression, in which
case each arm is a single expression and the whole switch
evaluates to the matched arm's value. Unlike statements, switch
expressions must handle every case (or have a default), and
the arms must produce a value:
enum Outcome {
Ok(int value),
Err(string error),
}
string describe(Outcome& outcome) {
return switch *outcome {
case Ok: "ok",
case Err error: error,
};
}
void main() {
var ok = Outcome.Ok(value = 1);
println(describe(ok)); // prints "ok"
}When a case binds no name, the switched variable narrows to the
matched case's payload inside that arm (a payloadless case narrows to
()), so its fields are available directly. The same
narrowing applies to == and != comparisons
against a case in if and while conditions:
enum Event {
Click(int x, int y),
Quit,
}
void handle(Event event) {
switch event {
case Click:
println("fire at ", event.x, ", ", event.y);
case Quit:
println("bye");
}
if event == Click {
println("fire at ", event.x, ", ", event.y);
}
}
void main() {
handle(Event.Click(x = 3, y = 4));
}defer
defer runs a statement when the current scope exits, for
cleanup before returning. See Defer and
destructors for details.
Planned features
- Add
fallthroughkeyword to explicitly enable switch case fallthrough behavior.