Control Flow: if, loop, while & for
Programs need to make decisions and repeat work: give a discount only to members, keep asking until the input is valid, print a line for every student in a list. These are jobs for control flow.
Rust has one way to choose (if, plus match, which gets its own lesson in Enums & match) and three kinds of loop: loop, while and for. Like most things in Rust, if and loop are expressions, so they can produce values.
if, else if and else
if runs a block when a condition is true. Add else if for more cases and else for everything else. The condition needs no brackets, but the curly brackets around each block are required.
fn main() {
let temperature = 38;
if temperature >= 40 {
println!("Heatwave alert: stay indoors");
} else if temperature >= 30 {
println!("Hot day: carry water");
} else {
println!("Pleasant weather");
}
let label = if temperature % 2 == 0 { "even" } else { "odd" };
println!("{temperature} is {label}");
}Hot day: carry water 38 is even
Rust checks the conditions from top to bottom and runs only the first block whose condition is true. The second if is used as an expression: its value goes straight into label. This replaces the ?: ternary operator of other languages. When if is used for a value, every branch must produce the same type, and an else is required.
The condition must be a real bool. Unlike C or JavaScript, Rust does not treat numbers as true or false, so if tickets { ... } with tickets being an integer is a compile error ("expected bool, found integer"). Write what you mean: if tickets > 0.
loop: repeat until you break
loop repeats forever until the code inside it runs break. You can hand a value back with break value, which makes the whole loop an expression:
fn main() {
let mut balance = 500;
let mut recharges = 0;
let count = loop {
balance -= 120;
recharges += 1;
if balance < 120 {
break recharges;
}
};
println!("Rs. 120 recharges possible: {count}");
println!("Balance left: Rs. {balance}");
}Rs. 120 recharges possible: 4 Balance left: Rs. 20
Trace it by hand: the balance goes 500, 380, 260, 140, 20. After the fourth recharge it is below 120, so the loop breaks with the value 4. loop is the right choice when the exit condition is found somewhere in the middle of the work, such as retrying a network request.
while: repeat while a condition is true
while checks its condition before each pass and stops as soon as it is false:
fn main() {
let mut countdown = 5;
while countdown > 0 {
print!("{countdown} ");
countdown -= 1;
}
println!("Lift-off!");
}5 4 3 2 1 Lift-off!
print! is like println! but does not add a new line, so the numbers stay on one line.
for: walk through ranges and collections
for is the loop you will write most often. It goes through every item of anything that can be iterated: a range of numbers, an array, a vector and many more.
fn main() {
for n in 1..=3 {
println!("7 x {n} = {}", 7 * n);
}
let cities = ["Rohtak", "Jaipur", "Pune"];
for city in cities {
println!("Delivering to {city}");
}
for (i, city) in cities.iter().enumerate() {
println!("{}. {city}", i + 1);
}
for n in (1..=4).rev() {
print!("{n} ");
}
println!();
}7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 Delivering to Rohtak Delivering to Jaipur Delivering to Pune 1. Rohtak 2. Jaipur 3. Pune 4 3 2 1
1..=3is an inclusive range: 1, 2 and 3.1..3is exclusive of the end: 1 and 2.for city in citiestakes each item of the array in turn. Here the array can still be used afterwards, because its items are simple copyable values..enumerate()pairs each item with its index (starting at 0), which is why we printi + 1..rev()reverses the range, andprintln!()with nothing inside just ends the line.
A for loop over a collection cannot go past the end, so it is safer than counting indexes by hand. Prefer for item in list over for i in 0..list.len().
continue, break and loop labels
continue skips the rest of the current pass and moves to the next one. break leaves the loop. With nested loops, break only leaves the innermost loop, unless you give the outer loop a label (a name starting with an apostrophe) and break to that label:
fn main() {
// 1 = seat taken, 0 = seat free
let hall = [[1, 1, 1], [1, 0, 1], [0, 0, 1]];
let mut seat = (0, 0);
'rows: for (r, row) in hall.into_iter().enumerate() {
for (c, taken) in row.into_iter().enumerate() {
if taken == 1 {
continue;
}
seat = (r + 1, c + 1);
break 'rows;
}
}
println!("First free seat: row {}, seat {}", seat.0, seat.1);
}First free seat: row 2, seat 2
The first row is full, so every pass hits continue. In the second row, the second seat is free; the code records it and break 'rows leaves both loops at once. Without the label, the outer loop would carry on and overwrite the answer with a seat from row 3.
Common mistakes
- Using a number as a condition. Write
if count > 0, notif count. - Off-by-one ranges.
1..10stops at 9. Use1..=10to include 10. - Missing
elsewhenifgives a value.let x = if ok { 1 };does not compile, because there would be no value whenokis false. - Forgetting to change the
whilevariable. Ifcountdown -= 1;is missing, the loop never ends. - Indexing in loops.
for i in 0..v.len()works but is easy to get wrong; iterate over the items directly.
Key points
if/else if/elsechooses a branch; conditions must bebool.ifis an expression, solet x = if c { a } else { b };replaces the ternary operator.looprepeats untilbreak, andbreak valuemakes the loop produce a value.whilerepeats while a condition holds;forwalks through ranges (a..b,a..=b) and collections.continueskips to the next pass, and labelled breaks such asbreak 'rowsleave nested loops.
Exercise
Print the FizzBuzz sequence for 1 to 15 with a twist for a cricket club: print "Six" for multiples of 6, "Four" for multiples of 4, "Boundary" for multiples of both, and the number otherwise. Then use a loop with break value to find the first number above 50 that is a multiple of both 4 and 6.
Show solution
fn main() {
for n in 1..=15 {
if n % 4 == 0 && n % 6 == 0 {
println!("Boundary");
} else if n % 6 == 0 {
println!("Six");
} else if n % 4 == 0 {
println!("Four");
} else {
println!("{n}");
}
}
let mut n = 51;
let first = loop {
if n % 4 == 0 && n % 6 == 0 {
break n;
}
n += 1;
};
println!("First Boundary above 50: {first}");
}1 2 3 Four 5 Six 7 Four 9 10 11 Boundary 13 14 15 First Boundary above 50: 60
The check for both multiples must come first; otherwise 12 would match the "Six" branch and never reach "Boundary". && means "and".
Quick Quiz
1. What does for n in 1..4 loop over?
a..b excludes the end value; use 1..=4 to include 4.
2. Which line is valid Rust?
An if used as a value needs an else, and Rust has no ?: operator.
3. What value does x get? let mut i = 0; let x = loop { i += 3; if i > 7 { break i * 2; } };
i becomes 3, 6, then 9; at 9 the loop breaks with 9 times 2, which is 18.
4. How do you leave an outer loop from inside an inner loop?
A labelled break exits the loop with that label, skipping any loops in between.
Learn Rust with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
