Skip to content

What Rust in Action says about flow control, run

Control flow › Flow control › The book's claims, run

Level: 101 · a companion to the section

One line: Twenty-one claims from §2.4 "Flow control" of Rust in Action (pp. 45–52), checked against rustc 1.98.0. Twelve hold as written and four hold with a condition. Five need a correction, and most of those are about why something happens rather than what happens.

The claims below are paraphrased. The section numbers and page numbers are the book's. Each row links to the lesson that teaches the construct in full.

The claims at a glance

# Where Claim Verdict Taught in
1 §2.4.1, p. 45 After for item in container, the container's lifetime has ended when the block finishes Correct it: the value is moved on the loop's first line, and only if it is not Copy for loops
2 §2.4.1, p. 46 for item in &collection is collection.iter(), and &mut collection is iter_mut() Holds for the types that implement it iter, iter_mut, into_iter
3 §2.4.1, p. 46 n..m is exclusive and n..=m is inclusive; by convention _ stands for a value you don't use Holds, and more than a convention: an unused i draws a warning for loops
4 §2.4.1, p. 47 Indexing in a loop costs a bounds check that direct iteration avoids Holds in a debug build; the optimizer removes it from 0..v.len() Loops without an index
5 §2.4.1, p. 47 A for over the collection guarantees nothing else changes it Holds Loops without an index
6 §2.4.2, p. 47 continue works as you would expect Holds; in a while it can also skip your increment continue
7 §2.4.3, p. 47 A while condition is any expression that evaluates to true or false Holds, and nothing else is accepted while loops
8 §2.4.3, p. 48 Rust programmers avoid while true and write loop Holds, and the compiler has reasons of its own loop
9 §2.4.4, p. 48 loop runs until a break, or until the program is stopped from outside Correct it: return, ?, a panic and process::exit leave it too loop
10 §2.4.5, p. 48 The (0..).zip(0..) loop with break stops once x + y > 100 Holds, at 51 and 51 break
11 §2.4.5, p. 49 break 'outer leaves all three nested loops Holds, at (0, 0, 1001) Loop labels
12 §2.4.5, p. 49, note 5 Labels work with continue as well Holds Loop labels
13 §2.4.5, p. 49 Rust has no goto keyword Holds, and goto is not even reserved Loop labels
14 §2.4.5, p. 49 For the C habit of jumping to cleanup code on an error, use loop labels Correct it: cleanup is Drop, which runs on every exit without a jump Loop labels
15 §2.4.6, pp. 49–50 if takes any expression that evaluates to a Boolean; there is no truthy or falsey Holds if expressions
16 §2.4.6, p. 49 The else block matches anything that has not already been matched Holds; later conditions are not even evaluated if expressions
17 §2.4.6, p. 51 Binding a name to a value with the assignment operator = is a statement Holds for let; an assignment x = 5 on its own is an expression of type () A block is an expression
18 §2.4.6, p. 50 The break keyword also returns a value Holds in a loop (and a labelled block) only break
19 §2.4.7, p. 51 match warns you when you have not considered an alternative Correct it: it is an error, E0004, as the book itself says a paragraph later match expressions
20 §2.4.7, p. 52 Listing 2.8: 42 \| 132 matches both values, _ matches the rest Holds match expressions
21 §2.4.7, p. 52 Many control structures, looping among them, are built from match underneath Correct it: for and ? are; while is a loop around an if Printing the HIR

Fifteen claims, run

Verified output of flow_control_claims_checked.rs — regenerated by tools/run_examples.py, never hand-typed.

1. after `for item in container`, the container's lifetime has ended
   an array of i32 is Copy, so it is still here: [1, 2, 3]
   a Vec was moved, but the name v takes a new value: [9]

2. `for item in &collection` is `for item in collection.iter()`
   Vec:     IntoIterator for &Vec gives [10, 20, 30], iter() gives [10, 20, 30]
   HashMap: IntoIterator for &HashMap gives [("ada", 36)], iter() gives [("ada", 36)]
   same items, because each type's impl calls iter(); &String has no such impl

3. `n..m` is exclusive and `n..=m` is inclusive
   0..10 ran 10 times, 0..=10 ran 11 times

5. a `for` over the collection guarantees it is not changed during the loop
   the index loop compiled, ran 3 passes, and never saw the pushed 4: [1, 2, 3, 4]

6. `continue` operates as you would expect
   in a while loop, a continue before i += 1: i is still 1 after 10 passes

9. `loop` runs until a `break`, or until the program is stopped from outside
   first_negative([4, -1, 7]) = Some(1)  (left by return)
   first_negative([4, 1, 7])  = None  (left by return)

10. `for (x, y) in (0..).zip(0..)` with `break` when x + y > 100
   stopped at x = 51, y = 51

11. `break 'outer` leaves all three loops once x + y + z > 1000
   stopped at (x, y, z) = (0, 0, 1001); x took the values [0]

12. labels work with `continue` too
   only row 3 finished its inner loop: [3]

13. Rust does not include the `goto` keyword
   `let goto = ...` compiles: goto is not reserved

14. use loop labels for the jump-to-cleanup pattern
   second step fails:
   drop: config
   -> Err("log could not be opened")
   both steps succeed:
   both open
   drop: log
   drop: config
   -> Ok(())

16. the `else` block matches anything that has not already been matched
   one-three-two; conditions evaluated: [42, 132]

17. assigning with `=` is a statement, so it has no value
   `let assigned = total = 5;` compiles: assigned is (), total went 1 -> 5

18. the `break` keyword also returns a value
   let n = loop { break 123; };  n is 123

20. Listing 2.8: `42 | 132` matches both, `_` matches the rest
   42: hit!
   132: hit!

Reading the ones that need more than the output:

  • 1. The book explains the error as the container's lifetime ending "once the block finishes". The array line shows that nothing ends for a Copy value, and the v = vec![9] line shows the name itself is still usable — what went away is the value, moved into the loop. The refusal below fires inside the loop body, before any block has finished.
  • 2. A for loop always calls IntoIterator::into_iter on what you give it. &Vec and &HashMap have an IntoIterator impl that happens to call iter(), so the table is right for them, but the equivalence is a promise each type makes, not a rule of the loop: &String makes no such promise.
  • 5. The index loop compiled, ran three passes against a Vec that ended with four items, and never saw the 4. 0..v.len() is evaluated once, before the first pass.
  • 6. In a for loop, continue still moves the iterator on. In a while loop the increment is your own line, and a continue above it skips it: i stays at 1 until the guard stops the loop.
  • 9. first_negative is a loop with no break in it, left by return both times. ? is a return in disguise, a panic unwinds out of it, and std::process::exit ends the process from inside it — without running destructors.
  • 11. x took the single value 0. The innermost loop reached 1001 on the first y, so the outer loops never advanced.
  • 13. goto is an ordinary identifier. The words Rust keeps back for future use are a separate list in the Reference ↗, and goto is not on it.
  • 14. In C, several failure points jump to one cleanup: label that frees what was acquired. In open_both, the early return drops config, and the success path drops log then config, in reverse order of creation. No label is involved. A labelled block with break 'label is the nearest Rust spelling of the jump itself, and Loop labels shows it, but it is never needed for cleanup.
  • 16. The closure records each condition it is asked. is(7) was never called: the chain stopped at the first true.
  • 17. "Binding a name to a value" is let, and let is a statement, as the refusal below shows. But the book names the = as the thing that makes it one, and an = without let is an assignment expression: let assigned = total = 5; compiles, and assigned is (). The book's third form, declarations, also covers more than fn, struct and enum: every item is one, including use, mod, const, static, impl and trait.

Claims the compiler settles

Each fence below is real rustc 1.98.0 output for the file it names. Lines that repeat the fence above them — the aborting summary and the rustc --explain pointer — are dropped.

1. The move is reported inside the loop, at the implicit into_iter() call:

Abridged — rustc 1.98.0, moved_inside_the_loop.rs
error[E0382]: borrow of moved value: `container`
 --> moved_inside_the_loop.rs:4:34
  |
2 |     let container = vec![1, 2, 3];
  |         --------- move occurs because `container` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     for item in container {
  |                 --------- `container` moved due to this implicit call to `.into_iter()`
4 |         println!("{item} of {}", container.len());
  |                                  ^^^^^^^^^ value borrowed here after move
  |
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
  |
3 |     for item in &container {
  |                 +

2. &String has no IntoIterator impl, so for c in &s is refused. Ask for s.chars() or s.bytes() instead.

Abridged — rustc 1.98.0, for_over_a_string_ref.rs
error[E0277]: `&String` is not an iterator
 --> for_over_a_string_ref.rs:3:14
  |
3 |     for c in &s {
  |              ^^ `&String` is not an iterator
  |
  = help: the trait `Iterator` is not implemented for `&String`
  = note: required for `&String` to implement `IntoIterator`

3. A loop variable nobody reads is a warning, and the fix it suggests is an underscore — _i, or _ alone, which binds nothing at all. The program still builds and prints 10.

Abridged — rustc 1.98.0, unused_loop_variable.rs
warning: unused variable: `i`
 --> unused_loop_variable.rs:3:9
  |
3 |     for i in 0..10 {
  |         ^ help: if this is intentional, prefix it with an underscore: `_i`
  |
  = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default

5. The for loop borrows v for its whole length, so a push inside it is refused:

Abridged — rustc 1.98.0, push_while_iterating.rs
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> push_while_iterating.rs:5:13
  |
3 |     for x in &v {
  |              --
  |              |
  |              immutable borrow occurs here
  |              immutable borrow later used here
4 |         if *x == 2 {
5 |             v.push(4);
  |             ^^^^^^^^^ mutable borrow occurs here

7 and 15. An integer is not a condition, in a while or an if:

Abridged — rustc 1.98.0, while_on_an_integer.rs
error[E0308]: mismatched types
 --> while_on_an_integer.rs:3:11
  |
3 |     while n {
  |           ^ expected `bool`, found integer
Abridged — rustc 1.98.0, if_on_an_integer.rs
error[E0308]: mismatched types
 --> if_on_an_integer.rs:3:8
  |
3 |     if item {
  |        ^^^^ expected `bool`, found integer

8. The book gives the convention and no reason. rustc gives three. The first is a warning that is on by default. The second: initialization analysis does not evaluate the condition, so a variable assigned inside while true counts as possibly uninitialized after it.

Abridged — rustc 1.98.0, while_true_is_not_loop.rs
warning: denote infinite loops with `loop { ... }`
 --> while_true_is_not_loop.rs:3:5
  |
3 |     while true {
  |     ^^^^^^^^^^ help: use `loop`
  |
  = note: `#[warn(while_true)]` on by default

error[E0381]: used binding `x` is possibly-uninitialized
 --> while_true_is_not_loop.rs:7:16
  |
2 |     let x: i32;
  |         - binding declared here but left uninitialized
3 |     while true {
  |           ---- if this condition isn't met and the `while` loop runs 0 times, `x` is not initialized
...
7 |     println!("{x}");
  |                ^ `x` used here but it is possibly-uninitialized
  |
  = note: when checking initialization, the compiler describes possible control-flow paths without evaluating whether branch conditions can actually have the values shown

The third: a while loop has type (), so a function cannot end in one, even one that can only leave by return. Write loop in either file and it compiles.

Abridged — rustc 1.98.0, while_true_returns_unit.rs
error[E0308]: mismatched types
 --> while_true_returns_unit.rs:2:5
  |
1 |   fn first() -> i32 {
  |                 --- expected `i32` because of return type
2 | /     while true {
3 | |         return 1;
4 | |     }
  | |_____^ expected `i32`, found `()`
  |
  = note: `while` loops evaluate to unit type `()`

17. let is the statement, and it cannot stand where a value is expected:

Abridged — rustc 1.98.0, let_is_a_statement.rs
error: expected expression, found `let` statement
 --> let_is_a_statement.rs:2:14
  |
2 |     let a = (let b = 1);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

18. break with a value is refused in a while loop:

Abridged — rustc 1.98.0, break_value_from_while.rs
error[E0571]: `break` with value from a `while` loop
 --> break_value_from_while.rs:5:9
  |
3 |     let x = while n < 3 {
  |             ----------- you can't `break` with a value in a `while` loop
4 |         n += 1;
5 |         break n;
  |         ^^^^^^^ can only break with a value inside `loop` or breakable block

19. The book's own match from p. 51 — 0, 10..=20, 40 | 80 — without its _ arm, on a u8. Not a warning:

Abridged — rustc 1.98.0, match_missing_arms.rs
error[E0004]: non-exhaustive patterns: `1_u8..=9_u8`, `21_u8..=39_u8`, `41_u8..=79_u8` and 1 more not covered
 --> match_missing_arms.rs:3:11
  |
3 |     match item {
  |           ^^^^ patterns `1_u8..=9_u8`, `21_u8..=39_u8`, `41_u8..=79_u8` and 1 more not covered
  |
  = note: the matched value is of type `u8`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms
  |
6 ~         40 | 80 => {},
7 +         _ => todo!()
  |

Claim 4, measured in assembly

The claim is about generated code, so it was checked in generated code: three functions summing a &[u64], one indexing 0..v.len(), one indexing 0..n with n a separate argument, one iterating directly. Each was compiled with rustc --edition 2024 -C opt-level=<0 or 3> --emit asm, and every panic_bounds_check call inside each function was counted. The source and the counting script are on Loops without an index.

Real run — rustc 1.98.0, 2026-09-16, identical on x86_64-apple-darwin and x86_64-unknown-linux-gnu
opt-level=0  sum_by_len  calls panic_bounds_check: 1
opt-level=0  sum_by_n  calls panic_bounds_check: 1
opt-level=0  sum_by_iter  calls panic_bounds_check: 0
opt-level=3  sum_by_len  calls panic_bounds_check: 0
opt-level=3  sum_by_n  calls panic_bounds_check: 1
opt-level=3  sum_by_iter  calls panic_bounds_check: 0

So the check the book warns about is real in a debug build. With optimization on, it survives only where the compiler cannot prove the index is in range — and in sum_by_n even that one is a single comparison of n - 1 against the length before the loop starts, not a check per element. Iterating directly never has one, at any level. The safety argument (claim 5) is the stronger reason to prefer it.

Claim 21, read from the HIR

RUSTC_BOOTSTRAP=1 rustc --edition 2024 -Zunpretty=hir while_in_hir.rs prints the program after rustc has rewritten its loops, for a while n < 3 { n += 1; } followed by for i in 0..2 { n += i; }:

Real run — rustc 1.98.0, 2026-09-16, the body of main
    let mut n = 0;
    loop { if n < 3 { n += 1; } else { break; } }
    {
        let _t =
            match into_iter(Range { start: 0, end: 2 }) {
                mut iter =>
                    loop {
                        match next(&mut iter) {
                            None {} => break,
                            Some {  0: i } => { n += i; }
                        }
                    },
            };
        _t
    }

Every loop is a loop underneath. The for loop is built from match, and so is ?, but the while loop is built from if. Printing the HIR walks through the for and ? rewrites line by line.

Tests for anything else you read about flow control

  • Does it say why a value is gone after a loop? The answer is a move into into_iter, visible inside the loop body. An explanation in terms of lifetimes ending or blocks finishing will mislead you about arrays and references.
  • Does it treat for x in &c as a language rule? It is an impl on &C, and a type can lack one.
  • Does it say while true and loop differ only in style? Try assigning a variable inside each and using it after.
  • Does it list break as the only way out of a loop? Count return, ?, a panic and process::exit.
  • Does it call a missing match arm a warning? It is E0004. The unreachable-arm check is the one that is a warning.
  • Does it recommend labels or goto-like jumps for cleanup? In Rust that is Drop.

See also

Sources

Po polsku

Sekcja 2.4 książki Rust in Action dobrze opisuje, co robią pętle i rozgałęzienia. Kiedy wyjaśnia, dlaczego tak się dzieje, bywa nieprecyzyjna. Sprawdziliśmy 21 twierdzeń z tej sekcji na rustc 1.98.0: dwanaście jest prawdziwych, cztery są prawdziwe pod warunkiem, a pięć wymaga poprawki. Najważniejsza poprawka dotyczy pętli for. Po for item in container wektora nie da się użyć nie dlatego, że po zakończeniu bloku „skończył się jego czas życia”. Pętla przenosi go do into_iter już w pierwszej linii, więc błąd E0382 pojawia się nawet w ciele pętli. Tablica [1, 2, 3] typu Copy zostaje nietknięta, a samej nazwie można później przypisać nową wartość.

Pozostałe poprawki są krótsze. loop kończy się nie tylko przez break, ale też przez return, ?, panikę albo process::exit. Sprzątanie w stylu C (skok do etykiety cleanup:) robi w Ruscie Drop, a nie etykieta pętli. Brakujące ramię match to błąd E0004, a nie ostrzeżenie. Wreszcie while pod spodem to loop z if, a nie z match. Do tego jedno zastrzeżenie: instrukcją jest let, ale samo przypisanie x = 5 (bez let) jest wyrażeniem typu (). Porada, żeby zamiast while true pisać loop, jest słuszna i nie chodzi tylko o styl. Kompilator ostrzega (while_true), nie uzna zmiennej przypisanej w while true za zainicjowaną (E0381) i nie pozwoli, by funkcja zwracająca i32 kończyła się taką pętlą (E0308).

Szukaj po polsku: Rust in Action errata · przeniesienie własności w pętli for · while true a loop · wyrażenie przypisania typu () · rust non-exhaustive patterns E0004