Control flow

Choosing, repeating, and matching. if is also an expression — it produces a value, so you can assign with it. In expression position it needs an else, and…

Choosing, repeating, and matching.

if / else

Section titled “if / else”
if score >= 90 {
println("A");
} else if score >= 80 {
println("B");
} else {
println("C");
}

if is also an expression — it produces a value, so you can assign with it. In expression position it needs an else, and both arms must produce the same type:

let grade = if score >= 90 { "A" } else { "B" };

Because an if is an expression, it can be an arm of another if and the value flows out through both:

let band = if score >= 90 {
if score >= 97 { "A+" } else { "A" }
} else {
"B"
};

One small trap: when the condition ends in a bare name, an empty body won’t parse — done { } reads as a struct literal, and a comment inside doesn’t change that. If you genuinely want a branch that does nothing, wrap the condition in parentheses or restructure it:

if (done) {
// nothing to do yet
}

while and loop

Section titled “while and loop”
let mut i = 0;
while i < 5 {
println(i);
i = i + 1;
}

while true { ... } repeats forever until you break:

let mut n = 0;
while true {
n = n + 1;
if n >= 3 { break; }
}

break exits the nearest loop; continue skips to the next iteration.

for

Section titled “for”

for iterates over a range or a collection:

for i in 0..5 {
println(i); // 0 1 2 3 4
}

(0..5 is exclusive of the upper bound; 0..=5 includes it.) You’ll use for over real collections once you meet lists and maps in Everyday programs.

match

Section titled “match”

match compares a value against patterns and runs the first that fits:

fn describe(n: Int) -> String {
return match n {
0 -> "zero",
1 -> "one",
_ -> "many",
};
}

_ is the wildcard — “anything else.” Matches must be exhaustive: the compiler rejects a match that doesn’t cover every possibility. For a Bool that means both true and false; for open-ended types it means a _ arm. This is a safety feature — you can’t forget a case and have it silently fall through.

match shines on enums (a type that’s one of several named shapes), which you’ll meet in Records & data. The arms can bind the data carried by each variant.

Matching on conditions instead of a value

Section titled “Matching on conditions instead of a value”

Sometimes there’s no single value to match on — you just want the first true condition out of several. Leave the scrutinee out:

let size = match {
n < 10 -> "small",
n < 100 -> "medium",
else -> "large",
};

Arms are tried top to bottom and the first true one wins, so order matters. else is the catch-all.

This is the same match — it’s shorthand for match true { _ if n < 10 -> ..., _ -> ... } — so everything above still applies: it works as a statement or an expression, arms can be blocks, and a block-bodied arm still needs its trailing comma.

Reach for it when you’re writing a ladder of if/else if that all produce the same kind of answer:

return match {
std::http::is_route(ctx, "GET", "/users") -> self.list(),
std::http::is_route(ctx, "GET", "/users/:id") -> self.show(id),
else -> not_found(),
};

Blocks have values

Section titled “Blocks have values”

A { ... } block’s last expression — written without a trailing ; — is the block’s value. That’s why if/match can be used as expressions, and why a function can end in a bare expression instead of return. A block whose last item does end in ; has no value, so it can’t stand where one is expected.

let label = {
let base = compute();
base + 1 // block evaluates to this
};

That’s the whole control-flow surface. Next we look at working with text: Strings & text.