Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 15 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: takes a , and the chain can give a value · , and each fit one kind of repetition · A range stops before its end, unless it is inclusive. With a pass: 2 in-depth parts and 2 interview answers, about 9 minutes.

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:

buzzer.rs: who answers each question Rust · buzzer.rs
// 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:

if_number.rs: a number as a condition Rust · if_number.rs
// 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

  1. Question 1 of 2 In buzzer.rs a round has the presses 1,600, 1,450 and −5 for Asha, Ben and Chen. What is the verdict?

    Choose one answer.

    A hint

    The conditions are tried from the top. Which one is the first to hold?

    Show the answer: Question 1 of 2

    Answer: False start, so the question is read again

    first = −5, so the first condition, first < 0, holds and its branch gives the verdict. An if chain runs the first branch whose condition holds and skips every branch below it.

  2. Question 2 of 2 What does rustc do with this code?

    Read the code, then choose one answer.

    let left = 2;
    if left {
        println!("seats remain");
    }
    Show the answer: Question 2 of 2

    Answer: It refuses it with E0308, expected bool, found integer

    if needs a bool. An integer is not one, so rustc reports mismatched types, E0308. Writing left > 0 says what the condition means.

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:

loops.rs: one job for each loop Rust · loops.rs
// 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:

Interactive · stage by stage How the for loop of loops.rs runs Step through the loop that adds up the scores. Watch next() hand out 7, 3 and 9, then None, which ends the loop.
How the for loop of loops.rs runs, step 5 of 5. Showing into_iter(scores) (iterator over 7, 3, 9), next() (gave None), Some(score): the body (score 9, total 19), None: the loop ends (total is 19). In focus: None: the loop ends.back for one moreinto_iter(scores)iterator over 7, 3, 9next()gave NoneSome(score): the bodyscore 9, total 19None: the loop endstotal is 19

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. An iterator is made. for score in scores first calls into_iter on the array, once. It gives an iterator over the three scores; the loop never counts positions itself.
  2. The first score. The loop calls next(), which returns Some(7). The pattern score takes the 7, and the body adds it: total is 7.
  3. Back for the next one. The body is done, so the loop asks again. next() returns Some(3), and the body makes the total 10.
  4. The last score. next() returns Some(9), the last score of the array, and the total becomes 19.
  5. None ends the loop. The next call of next() finds no score left and returns None. 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

  1. Question 1 of 2 What is first after let mut n = 0; let first = loop { n += 1; if n * n > 50 { break n; } };?

    Type a number.

    A hint

    Square 1, 2, 3 … until the square is more than 50.

    Show the answer: Question 1 of 2

    Answer: 8

    7 × 7 = 49 is not more than 50, and 8 × 8 = 64 is, so break n runs with n = 8, and 8 becomes the value of the loop, as 32 did in loops.rs.

  2. Question 2 of 2 A program asks for a seat number until the user types one that is free, then books that seat. Which loop fits best?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A loop that ends with break and the seat number, so the loop's value is the seat

    loop repeats until a break, and break value makes the seat the value of the whole loop. while and for can end without a break, so their value is always ().

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:

ranges.rs: what each range gives Rust · ranges.rs
// 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

  1. Question 1 of 2 How many numbers does (1..=10).step_by(3) give?

    Type a number.

    A hint

    Start at 1, keep every third number, and remember that ..= includes the end.

    Show the answer: Question 1 of 2

    Answer: 4 numbers

    The range gives 1 to 10, and step_by(3) keeps 1, 4, 7 and 10: four numbers. With 1..10 it would stop at 7.

  2. Question 2 of 2 Which loop visits seats 1 to 12, all twelve of them?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: for seat in 1..=12

    a..b stops before b, and a..=b includes it. Seats counted from 1 through 12 are 1..=12.

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.

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.

  1. Question 1 of 10 Why must every branch of an if that gives a value have the same type?

    Choose one answer.

    Show the answer: Question 1 of 10

    Answer: The if is one expression, so it has one type, whichever branch runs

    An if used as a value is an expression with a single type, so every branch must produce a value of that type.

  2. Question 2 of 10 What is fare when km is 300?

    Read the code, then choose one answer.

    let fare = if km > 50 {
        "regional"
    } else if km > 200 {
        "long distance"
    } else {
        "local"
    };
    Show the answer: Question 2 of 10

    Answer: "regional"

    An if chain tries its conditions from the top. Put the narrower test, km > 200, before the wider one, or the wider one catches every case first.

  3. Question 3 of 10 Why does rustc refuse if x = 5 { … }, when C compiles the same test written if (x = 5)?

    Choose one answer.

    Show the answer: Question 3 of 10

    Answer: An assignment's value is (), not a bool, so it cannot be a condition; rustc suggests ==

    if needs a bool. x = 5 is an assignment whose value is (), so rustc reports mismatched types and points at ==, the comparison that was meant.

  4. Question 4 of 10 What is the value of a while loop?

    Choose one answer.

    Show the answer: Question 4 of 10

    Answer: Always (), because a while loop can end without a break

    Only loop gives a value with break. while and for end when their condition fails or their items run out, so their value is ().

  5. Question 5 of 10 What is code after this loop?

    Read the code, then choose one answer.

    let mut tries = 0;
    let code = loop {
        tries += 1;
        if tries == 3 {
            break tries * 100;
        }
    };
    Show the answer: Question 5 of 10

    Answer: 300

    break value ends a loop and makes value the loop's result. Here it runs once, on the third pass, with 3 × 100.

  6. Question 6 of 10 Why can a for loop over an array never read past its end?

    Choose one answer.

    Show the answer: Question 6 of 10

    Answer: It asks an iterator for each item, and the iterator answers None when no item is left, which ends the loop

    for calls into_iter once, then next until it returns None. Indexing is left to the iterator, so the loop body never sees a position past the end.

  7. Question 7 of 10 How many numbers does the range 3..8 give?

    Type a number.

    Show the answer: Question 7 of 10

    Answer: 5 numbers

    3..8 gives 3, 4, 5, 6 and 7: 8 − 3 = 5 numbers, because a range with .. stops before its end.

  8. Question 8 of 10 What does for n in (1..=4).rev() give n, in order?

    Choose one answer.

    Show the answer: Question 8 of 10

    Answer: 4, 3, 2, 1

    rev() goes through the numbers of 1..=4 backwards: 4, 3, 2 and 1, as (1..=5).rev() gave 5 down to 1 in ranges.rs.

  9. Question 9 of 10 What happens with for i in 5..5 { println!("{i}"); }?

    Choose one answer.

    Show the answer: Question 9 of 10

    Answer: Nothing is printed; the range is empty and the loop body never runs

    A range whose start is not below its end is empty. Loops over it run zero times, which is often exactly right, for example for a list of zero items.

  10. Question 10 of 10 A row has seats 1 to 12, and the aisle seats are 3, 6, 9 and 12. Which expression gives exactly those?

    Choose one answer.

    Show the answer: Question 10 of 10

    Answer: (3..=12).step_by(3)

    step_by always keeps the first number, so start the range at the first seat you want, 3, and use ..= to include the last one, 12.

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 ().

  1. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A condition must be a bool, the first branch whose condition holds runs, and an if chain is an expression whose branches all give the same type.
  • for goes through items, while repeats while a condition holds, and loop repeats until a break, the only break that can give a value.
  • a..b stops before b, a..=b includes 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

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.