Control Flow
A program that runs every line exactly once, top to bottom, can't do much. Control flow is how code decides which lines run, and how many times: choosing between branches, repeating until something is done, or walking through a list of values.
Rust has one way to branch, if, and three loops: loop, while and for.
Most of it looks like other languages, with one twist from the previous lesson:
if and loop are expressions, so they can produce values.
if and else
An if runs its block only when its condition is true. An optional else
block runs otherwise:
fn main() {
let battery = 15;
if battery < 20 {
println!("Battery low, plug in soon");
} else {
println!("Battery fine");
}
}Battery low, plug in soonThere are no parentheses around the condition, but the braces around each block are always required, even for a single line.
To check several conditions in turn, chain them with else if. The first
condition that is true wins, and the rest are skipped:
fn main() {
let wind_kmh = 48;
if wind_kmh >= 75 {
println!("Storm warning");
} else if wind_kmh >= 40 {
println!("Strong wind");
} else if wind_kmh >= 20 {
println!("Breezy");
} else {
println!("Calm");
}
}Strong wind48 is also at least 20, but the >= 40 branch comes first, so only
Strong wind is printed. Order the conditions from most to least specific.
Conditions must be bool
The condition must be a bool. Rust never treats a number, an empty string or
anything else as "truthy", so compare explicitly: if count != 0, not
if count. You can combine conditions with &&, || and ! from the
primitive types lesson:
fn main() {
let age = 34;
let has_ticket = true;
if age >= 18 && has_ticket {
println!("Welcome in");
}
if !has_ticket || age < 18 {
println!("Sorry, not tonight");
}
}Welcome inif is an expression
In Rust, if produces a value: the value of whichever block ran. That means it
can go on the right side of a let, where other languages would need a
ternary operator:
fn main() {
let items = 3;
let label = if items == 1 { "item" } else { "items" };
println!("{items} {label} in your cart");
let delivery = if items >= 5 { 0.0 } else { 4.99 };
println!("delivery: {delivery}");
}3 items in your cart
delivery: 4.99Each block ends in an expression with no semicolon, just like a function body.
Because the result is bound to one variable with one type, every branch has to
produce a value of the same type, and there has to be an else: without
one, the if would have no value when the condition is false.
This works anywhere a value is expected, including as the last expression of a function, which is a common way to return one of several results:
fn ticket_price(age: u32) -> u32 {
if age < 12 {
8
} else if age >= 65 {
10
} else {
15
}
}
fn main() {
for age in [7, 40, 70] {
println!("age {age}: {}", ticket_price(age));
}
}age 7: 8
age 40: 15
age 70: 10Repeating with loop
loop runs its body forever, until you stop it with break:
fn main() {
let mut attempts = 0;
loop {
attempts += 1;
println!("connecting, attempt {attempts}");
if attempts == 3 {
break;
}
}
println!("connected");
}connecting, attempt 1
connecting, attempt 2
connecting, attempt 3
connectedcontinue skips the rest of the current iteration and starts the next one.
loop is an expression too. Give break a value and the loop evaluates to
it, which is handy when you repeat something until it produces a result:
fn main() {
let mut n = 27;
let mut steps = 0;
let steps_to_one = loop {
if n == 1 {
break steps;
}
n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
steps += 1;
};
println!("27 reaches 1 in {steps_to_one} steps");
}27 reaches 1 in 111 stepsLoop labels
break and continue apply to the innermost loop. To break out of an outer
loop from inside a nested one, give the outer loop a label, written with a
leading apostrophe, and name that label in the break:
fn main() {
let grid = [[3, 8, 1], [4, 0, 7], [9, 2, 5]];
'rows: for row in grid {
for cell in row {
if cell == 0 {
println!("found an empty cell, stopping");
break 'rows;
}
println!("cell {cell}");
}
}
}cell 3
cell 8
cell 1
cell 4
found an empty cell, stoppingA plain break there would only end the inner loop, and the outer one would
carry on with the next row.
while
A while loop checks a condition before every iteration and stops as soon as
it is false. It is loop plus an if and a break, written more directly:
fn main() {
let mut balance = 1000.0;
let mut years = 0;
while balance < 2000.0 {
balance *= 1.07;
years += 1;
}
println!("doubled after {years} years: {balance:.2}");
}doubled after 11 years: 2104.85Use while when you don't know in advance how many times to loop, only when to
stop. Unlike loop, a while loop can't break with a value: it can also end
because the condition turned false, and then there would be no value.
for and ranges
for runs its body once for every item in a sequence. You have already used it
on arrays. The other thing you will loop over constantly is a range:
a..bcounts fromaup to, but not including,b;a..=bincludesb.
fn main() {
for i in 0..3 {
print!("[{i}]");
}
println!();
for i in 1..=3 {
print!("[{i}]");
}
println!();
}[0][1][2]
[1][2][3]The exclusive form fits indexes: 0..array.len() covers every valid index and
stops before the length. The inclusive form fits human counting, like
"from 1 to 10".
Ranges have methods to change how they are walked. rev() goes backwards and
step_by(n) takes every nth value:
fn main() {
for i in (1..=3).rev() {
print!("{i}... ");
}
println!("go!");
for minute in (0..60).step_by(15) {
println!("10:{minute:02}");
}
}3... 2... 1... go!
10:00
10:15
10:30
10:45The parentheses around the range are needed so the method applies to the whole range, not just its end.
A range is also a value you can store and ask questions of, for example with
contains:
fn main() {
let working_hours = 9..17;
let hour = 18;
println!("open at {hour}:00? {}", working_hours.contains(&hour));
}open at 18:00? falseWhich loop to use
forwhen you have something to walk through: a range, an array or, later, aVecor an iterator. It is the most common loop in Rust, and it can't go out of bounds.whilewhen you loop until a condition changes.loopwhen the exit is somewhere in the middle of the body, or when the loop should produce a value.
A for over a collection is also safer and clearer than a while that counts
an index by hand: there is no counter to forget to increment and no index to
get wrong.
Later lessons add one more way to branch: match, which compares a value
against a list of patterns. It gets its own lesson with enums.
Common mistakes
A condition that isn't a bool
fn main() {
let unread = 4;
if unread {
println!("You have mail");
}
}Write the comparison you mean: if unread > 0.
Branches with different types
fn main() {
let stock = 0;
let status = if stock > 0 { stock } else { "sold out" };
println!("{status}");
}The compiler reports the first branch's type as the expected one. Make every
branch produce the same type, here for example by formatting the number into
a String in one branch and turning the text into a String in the other.
Using if without else as a value
fn main() {
let points = 120;
let bonus = if points > 100 {
10
};
println!("{bonus}");
}Without else, the if evaluates to () when the condition is false, which
doesn't match 10. Add an else branch with a value of the same type.
Breaking out of while with a value
fn main() {
let mut n = 1;
let first_big = while n < 1000 {
n *= 3;
if n > 100 {
break n;
}
};
println!("{first_big:?}");
}Only loop can return a value from break. Rewrite it as a loop, or keep
the while and read n after it ends.
Summary
if condition { ... } else if other { ... } else { ... }picks the first branch whose condition is true. Conditions must bebool.ifis an expression:let x = if c { a } else { b };. Every branch needs the same type, and a value-producingifneeds anelse.looprepeats untilbreak;break valuemakes the loop evaluate tovalue.continuejumps to the next iteration.- Labels like
'outerletbreakandcontinuetarget an outer loop. whileloops while a condition holds.for x in rangewalks a range:a..bexcludesb,a..=bincludes it.rev()andstep_by(n)change the direction and step.- Prefer
forover hand-counted indexes.
Practise what you read
Challenges that use the ideas from this lesson.