Rust (2024 edition) Module 2 – Values, types, functions and control flow
Control flow: if, loop, while and for
Branch with if as a value, repeat with loop, while and for, write ranges that stop where you mean, and leave nested loops with labels in Rust.
What you will learn
- Write if and else if chains on bool conditions, and use an if as a value
- Choose between loop, while and for, and return a value from a loop with break
- Predict the numbers a range gives with .., ..=, rev and step_by
- Use a label to break out of or continue an outer loop, and return a value from a labelled block
- Write loops over collections and loop bounds that neither move the data nor overflow
Before you start
What you’ll learn · Before you start
What you will learn
- Write if and else if chains on bool conditions, and use an if as a value
- Choose between loop, while and for, and return a value from a loop with break
- Predict the numbers a range gives with .., ..=, rev and step_by
- Use a label to break out of or continue an outer loop, and return a value from a labelled block
- Write loops over collections and loop bounds that neither move the data nor overflow
Before you start
On this page
In C, if (presses) { … } runs whenever presses is not 0, and JavaScript counts every number but 0 and NaN as
true. In Rust, if presses { … } is error E0308, mismatched types: a condition must be a bool, and no rule turns a
number into one for you.
This lesson branches with if, else if and else, uses an if as a value, and picks one of Rust’s three loops for
each job: for for the items of a collection or a range, while for as long as a condition holds, and loop until a
break. It ends with ranges, the numbers a for loop most often counts through, and the one character that decides
whether a range includes its end. Every result is a run on Rust 1.99.0.
if takes a bool, and the chain can give a value
A quiz has three contestants and a buzzer. For each question, this program decides who answers from the three press times, in milliseconds after the question ended:
// A quiz buzzer: who answers each question, decided by if, else if and else.
fn main() {
// Milliseconds after the question ended; a negative time is a press before it ended.
let rounds = [[410, 385, 902], [-20, 300, 310], [1_500, 1_500, 1_500]];
for [asha, ben, chen] in rounds {
let first = asha.min(ben).min(chen);
let verdict = if first < 0 {
"false start, so the question is read again"
} else if first >= 1_500 {
"nobody pressed in time"
} else if first == asha {
"Asha answers"
} else if first == ben {
"Ben answers"
} else {
"Chen answers"
};
println!("{asha:>5} {ben:>5} {chen:>5} {verdict}");
}
} Output
410 385 902 Ben answers -20 300 310 false start, so the question is read again 1500 1500 1500 nobody pressed in time
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O buzzer.rs && ./buzzer
The conditions are tried from the top, and the first one that holds picks its branch. In the second round −20 ms is a
false start, so that branch wins, although Ben pressed at 300. The whole chain is an expression, as blocks were in the
last lesson, so its value goes straight into verdict, and every branch must give the same type, here &str.
Each condition must be a bool. A number is not one, and Rust has no rule that makes 0 false and every other number
true:
// C treats every number but 0 as true, and JavaScript every number but 0 and NaN. Rust does neither.
fn main() {
let presses = 3;
if presses {
println!("somebody pressed");
}
} Output (exit status 1)
Printed as an error (standard error)
error[E0308]: mismatched types
--> if_number.rs:4:8
|
4 | if presses {
| ^^^^^^^ expected `bool`, found integer
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O if_number.rs && ./if_number
Write the comparison you mean, if presses > 0. The rule also catches a classic C slip: if x = 5 assigns instead of
comparing, and an assignment’s value is (), so rustc refuses it and suggests ==.
Checkpoint
Check: Conditions and if as a value
loop, while and for each fit one kind of repetition
Each of the three loops decides in its own way when to stop. for stops when the items of an array, a range or
another collection run out. while stops when its condition fails. loop stops only at a break, and it is the only
one whose break can hand back a value. This program gives each the job it suits:
// Rust's three loops, each on the job it suits.
fn main() {
// for: every item of an array, a range or another collection.
let scores = [7, 3, 9];
let mut total = 0;
for score in scores {
total += score;
}
println!("for: the scores add up to {total}");
// while: as long as a condition holds.
let mut seats = 10;
let mut groups = 0;
while seats >= 4 {
seats -= 4;
groups += 1;
}
println!("while: {groups} groups of 4, with {seats} seats left over");
// loop: until a break, which can hand the loop a value.
let mut n = 0;
let first = loop {
n += 1;
if n * n > 1_000 {
break n;
}
};
println!("loop: {first} is the first number whose square is over 1000");
} Output
for: the scores add up to 19 while: 2 groups of 4, with 2 seats left over loop: 32 is the first number whose square is over 1000
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O loops.rs && ./loops
The while loop took 4 seats as long as at least 4 were left: twice, leaving 2. The loop stopped at 32, because
31 × 31 = 961 and 32 × 32 = 1,024, and break n made 32 the value of the whole loop, bound to first. A while
or for loop can also end with no break at all, when its condition fails or its items run out, so there would be
no value to give: their value is always ().
A for loop does not count positions. It asks an
iterator: A value that hands out items one at a time: each call of its next() gives Some(item), or None once no item is left. A for loop gets one from the collection it is given and calls next() until it returns None. for the next item until there is none, as the Rust Reference defines it. Step
through the loop that added up the scores:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- An iterator is made.
for score in scoresfirst callsinto_iteron the array, once. It gives an iterator over the three scores; the loop never counts positions itself. - The first score. The loop calls
next(), which returnsSome(7). The patternscoretakes the 7, and the body adds it: total is 7. - Back for the next one. The body is done, so the loop asks again.
next()returnsSome(3), and the body makes the total 10. - The last score.
next()returnsSome(9), the last score of the array, and the total becomes 19. - None ends the loop. The next call of
next()finds no score left and returnsNone. The loop ends without a counter or a test written by you, and the program prints 19.
Because for reads items rather than positions, it cannot run past the end of an array, and it has no counter that
could be off by one. When loops are nested, a
A name such as 'rows, written before a loop with a colon, that break and continue can name to act on that loop instead of the innermost one.
such as 'rows lets break or continue reach the outer loop instead of the inner one. The label goes before the
loop with a colon, as in 'rows: for row in coach, and a block can carry a label too, so that break can leave it
early with a value.
Key idea
Use for to go through items, while to repeat while a condition holds, and loop when only the body knows when
to stop; only loop can give a value with break.
Checkpoint
Check: Choosing loop, while or for
A range stops before its end, unless it is inclusive
for seat in 1..12 visits 11 seats, not 12. A range: A value written a..b or a..=b that stands for the whole numbers from a up to b: a..b stops before b, and a..=b includes it. A for loop over a range visits its numbers in order. a..b gives the whole numbers from a
up to, but not including, b, and the inclusive range a..=b includes b. Two methods change the order and the step:
// What each range gives. collect() gathers its numbers into a Vec, a list that can grow, so {:?} can print them.
fn main() {
let upto: Vec<u32> = (1..5).collect();
let through: Vec<u32> = (1..=5).collect();
let down: Vec<u32> = (1..=5).rev().collect();
let every_third: Vec<u32> = (0..10).step_by(3).collect();
let empty: Vec<u32> = (5..5).collect();
println!("1..5 {upto:?}");
println!("1..=5 {through:?}");
println!("(1..=5).rev() {down:?}");
println!("(0..10).step_by(3) {every_third:?}");
println!("5..5 {empty:?}");
// A range in a for loop: every fourth seat of a row of 12, from seat 1.
for seat in (1..=12).step_by(4) {
println!("window seat {seat}");
}
} Output
1..5 [1, 2, 3, 4] 1..=5 [1, 2, 3, 4, 5] (1..=5).rev() [5, 4, 3, 2, 1] (0..10).step_by(3) [0, 3, 6, 9] 5..5 [] window seat 1 window seat 5 window seat 9
Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O ranges.rs && ./ranges
1..5 gave four numbers, 5 − 1 of them, and 1..=5 gave five. .rev() walks the same numbers backwards, and
.step_by(3) keeps every third one, starting with the first. An empty range such as 5..5 is no error: a loop over
it simply does not run. The last loop counts through a row of 12 seats the usual way, 1 through 12 with ..=, and
.step_by(4) keeps seats 1, 5 and 9.
Rust 1.96: new range types
Rust 1.96 added core::range::Range and its relatives. Unlike the older ranges of std::ops, they are Copy and
hand out their numbers through IntoIterator instead of being iterators themselves. The syntax 0..n still makes
the older types, until a future edition switches it.
Checkpoint
Check: The numbers a range gives
Labelled loops and blocks, run on a seat map Pro: opens with a Pro, Premium or Ultimate pass
Nested loops need a way to stop both at once, or to move the outer one on early. This part searches a coach’s seat map with labels, gets a value out of a labelled block, and runs the label mistakes that rustc refuses.
In this part:
- A labelled block that stops a two-level search at its first match
- continue 'rows moves the outer loop on, and rustc checks every label
2 runnable examples (Rust) · about 5 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Loops that stay correct: moves, indexes and overflow Pro: opens with a Pro, Premium or Ultimate pass
Three loop bugs either stop the build with a message that is hard to read at first or appear only for some inputs. This part reproduces each one, measures the worst of them, and ends with four loop drills that use every kind of loop.
In this part:
- A for loop takes its collection unless you lend it
- Index loops, and what Clippy’s needless_range_loop says
- An overflowing loop bound, reproduced on the largest u32 prime
- Exercise: four loop drills, with their edge cases
- Which loop for which job, with this lesson’s cases
8 runnable examples (Python, Rust) · about 7 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Practise: branches, loops and ranges
These questions are about conditions, choosing a loop and the numbers a range gives. Every choice comes with the reason it is right or wrong.
Practice that adapts to you
Practise this lesson
10 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Warm-up (fresher): when do you use loop, while or for in Rust?
Use for to go through the items of a collection or a range: it asks an iterator for each item, so it cannot run
past the end and needs no counter. Use while when a condition decides how long to repeat, such as “while seats are
left”. Use loop when only the body knows when to stop, such as retrying until an answer is valid. Only loop can
return a value, with break value, because it ends only through a break; while and for can end without one,
so their value is always ().
-
Fresher to mid
A booking function scans a 40-row seat map with two nested for loops and sets a found variable whenever it sees a free window seat. Testers report that it offers the last free window seat instead of the first. Why, and how would you write the search?
-
Mid to senior
A prime check for u64 values loops while
i * i <= n. A fuzz test finds that a debug build panics with "attempt to multiply with overflow" on an input close to u64::MAX. What is wrong, what would an optimised build do, and how do you fix and test it?
Key takeaways
- A condition must be a
bool, the first branch whose condition holds runs, and anifchain is an expression whose branches all give the same type. forgoes through items,whilerepeats while a condition holds, andlooprepeats until abreak, the onlybreakthat can give a value.a..bstops beforeb,a..=bincludes it, and.rev()and.step_by(k)change the order and the step.
Terms in this lesson
- iterator
- A value that hands out items one at a time: each call of its next() gives Some(item), or None once no item is left. A for loop gets one from the collection it is given and calls next() until it returns None.
- range
- A value written a..b or a..=b that stands for the whole numbers from a up to b: a..b stops before b, and a..=b includes it. A for loop over a range visits its numbers in order.
References
- The Rust Programming Language: Control Flow (The Rust Project)
- The Rust Reference: if expressions (The Rust Project)
- The Rust Reference: Loops and other breakable expressions (The Rust Project)
- The Rust Reference: Range expressions (The Rust Project)
- The Rust Standard Library: Iterator::step_by (The Rust Project)
- The Rust Standard Library: std::range (The Rust Project)
- Announcing Rust 1.96.0 (The Rust Project)
- Announcing Rust 1.65.0 (The Rust Project)
- The Rust Standard Library: Vec::pop (The Rust Project)
- Rust error codes index: E0308 (The Rust Project)
- Rust error codes index: E0571 (The Rust Project)
- Rust error codes index: E0382 (The Rust Project)
- Rust error codes index: E0696 (The Rust Project)
- Clippy lints: needless_range_loop (The Rust Project)
Report a problem with this lesson
Kept only in this browser. Your Learn progress