# 7.x Chapter 7 summary and quiz > Control flow reviewed: match, the three loops, jumps, recursion, halts, and dependencies, plus a quiz that builds real programs. Source: https://learnrust.net/chapter-7/chapter-7-summary-and-quiz/ This was the chapter where your programs stopped reading like scripts and started reading like *decisions*: branching, repeating, and ending on their own judgment. It's also the chapter where the course's first complete program shipped. The review, then a quiz with two programs worth keeping. ## Quick review A program's **execution path** is the sequence of statements it actually runs, and a **straight-line program** has only one. Control flow statements create alternatives: when execution goes somewhere other than straight ahead, it has **branched**. Rust's toolkit: conditionals (`if`, `match`), loops (`loop`, `while`, `for`), jumps (`break`, `continue`, `return`), and halts. What the toolkit deliberately omits (goto, do-while, switch, and exceptions) all reduce to the same trade: delete the bug-prone construct, keep its one good job ([7.1](@/chapter-7/control-flow-introduction.md)). An `if`/`else if` chain takes its *first* true arm and ignores the rest; separate `if` statements each get evaluated. Early returns flatten functions by handling the rejections first, and logical operators flatten nested conditions. A variable born inside an arm dies at the arm's closing brace; when arms exist to produce a value, the `if`-as-expression delivers it past the braces ([7.2](@/chapter-7/working-with-if-and-else.md)). A **match** evaluates its **scrutinee** once and tests it against each arm's **pattern**, top to bottom, first fit wins, no fallthrough. `_` matches anything; `|` combines patterns into one arm. A match must be **exhaustive**: the compiler proves your arms cover every possible value (E0004 names exactly what you missed), which means a match doesn't just check the cases you thought of, it checks the completeness of your thinking. Patterns that bind, guard, and destructure arrive with chapter 11 ([7.3](@/chapter-7/introduction-to-match.md)). A **while** checks before every **iteration** and runs zero times if the condition starts false. `loop` is the deliberate infinite loop (the compiler will tell you to use it if you write `while true`), escaped by `break`, and `loop` plus a conditional `break` replaces do-while. On unsigned counters, `while i >= 0` is always a bug, and rustc labels it ([7.4](@/chapter-7/loop-and-while.md)). A **for** loop walks a sequence with no exposed bookkeeping: `0..n` is **half-open** (n iterations), `0..=n` **inclusive**, `.rev()` reverses, `.step_by(k)` skips; the loop variable is a fresh binding each pass. Default to `for` when the values are known up front, `while` for re-evaluated conditions, `loop` for "until I say" ([7.5](@/chapter-7/for-loops-and-ranges.md)). **break** ends its loop, **continue** ends its iteration, and both can target an enclosing loop by **label** (`break 'outer`), the civilized replacement for goto's last legitimate job. From a `loop` (only), `break` can carry a value out, making the loop an expression and retiring a whole genre of flag variables ([7.6](@/chapter-7/break-continue-and-loop-labels.md)). A **recursive** function calls itself, shrinking toward a **base case**; without a reachable one, each unfinished call holds its ledge of memory until the stack runs out (a **stack overflow**). Prefer loops where they're equally clear, which for plain counting they always are; recursion shines when the problem is self-similar, and Rust doesn't promise tail-call optimization ([7.7](@/chapter-7/introduction-to-recursion.md)). Programs report an **exit code** at the end: 0 for success, nonzero for failure, read by scripts and CI rather than humans. `std::process::exit(code)` halts immediately from anywhere, skipping cleanup and unflushed output; `panic!` crashes on purpose with a message addressed to the programmer, for states that should be impossible (expected failures are chapter 12's `Result` business) ([7.8](@/chapter-7/halting-your-program-early.md)). A **PRNG** is deterministic scrambling: the **seed** sets its state, and same seed means same sequence, forever. Real randomness-shaped variety comes from OS-seeded generators, via your first **dependency**: `cargo add rand` writes one semver line into `Cargo.toml`, `Cargo.lock` pins exact versions for reproducible builds, and `rand::random_range(1..=100)` (or a reused `rand::rng()` generator) does the rolling ([7.9](@/chapter-7/adding-a-dependency-random-numbers.md)). All of it converged in the guessing game, whose optimal strategy you already knew from debugging: halve the range every guess. Its name is **binary search**, and 2⁷ = 128 is why 7 guesses crack 100 numbers ([7.10](@/chapter-7/project-number-guessing-game.md)). ## Quiz time **Question #1** Three quick ones, one sentence each: a) `loop { ... }` and `while true { ... }` run identically. Why does the compiler (and this course) still insist on `loop`? b) You're summing user inputs until the user types `0`. Which loop, and why? c) You're printing a 12-row multiplication table. Which loop, and why?
Show solution a) `loop` declares there's no exit condition, so the compiler treats it specially: only `loop` can `break` with a value, because every exit from a `loop` is a `break`. (`while true` additionally draws a warning.) b) A `loop` with a sentinel `break` (or break-with-value): iteration count depends on a decision made *inside* the loop. c) A `for` over `1..=12`: the values to visit are known up front, and the loop does the counting bookkeeping for you.
**Question #2** What does this print? ```rust fn main() { 'outer: for i in 1..=3 { for j in 1..=3 { match (i * j) % 4 { 0 => break 'outer, 1 => continue, _ => print!("{} ", i * j), } } println!("row {i} done"); } println!("end"); } ```
Show solution ``` 2 3 row 1 done 2 end ``` Row 1: products 1, 2, 3; the 1 hits `continue` (skipping its print), 2 and 3 print, the row completes. Row 2: product 2 prints, then product 4 hits `break 'outer`, abandoning both loops at once, so neither "row 2 done" nor row 3 happens; execution lands on the final `println!`. (Note `continue` targeted the *inner* loop, its innermost enclosing one, and that a `match` arm can hold a jump statement just fine.)
**Question #3** Predict the compiler's reaction to each, precisely: ```rust // Program A fn main() { let on = true; match on { true => println!("lights on"), } } ``` ```rust // Program B fn main() { let result = for i in 1..=10 { if i * i > 50 { break i; } }; println!("{result}"); } ```
Show solution Program A: E0004, non-exhaustive patterns: `false` not covered. A `bool` has exactly two values and the match handles one; exhaustiveness is checked against the type, and the fix is a `false` arm (or `_`). Program B: E0571, `break` with value from a `for` loop. A `for` can end by exhausting its range without ever reaching the `break`, so the loop can't promise a value; only `loop` can. (Rewrite with `let mut`/plain `break`, or a `loop` with its own counter.)
**Question #4** Chapter 4's falling-ball program ([4.x](@/chapter-4/chapter-4-summary-and-quiz.md), question #3) had a hardcoded flaw: it reported the ball's height at t = 0 through 5 because *we* worked out that a 100-meter drop takes about 5 seconds. Replace the six `report` calls with a loop that keeps reporting, one second at a time, until the ball has landed, for any tower height. (Reuse `ball_height`, `report`, and `read_number` from chapter 4 unchanged.)
Show solution ```rust fn ball_height(start: f64, seconds: f64) -> f64 { let fallen = 9.8 * seconds * seconds / 2.0; let height = start - fallen; if height < 0.0 { 0.0 } else { height } } fn report(start: f64, seconds: f64) { let height = ball_height(start, seconds); if height > 0.0 { println!("At {seconds} seconds, the ball is at height: {height} meters"); } else { println!("At {seconds} seconds, the ball is on the ground."); } } fn read_number() -> f64 { let mut input = String::new(); std::io::stdin() .read_line(&mut input) .expect("failed to read input"); input.trim().parse().expect("that wasn't a number") } fn main() { println!("Enter the height of the tower in meters:"); let start = read_number(); let mut seconds = 0.0; loop { report(start, seconds); if ball_height(start, seconds) <= 0.0 { break; } seconds += 1.0; } } ``` ``` Enter the height of the tower in meters: 100 At 0 seconds, the ball is at height: 100 meters At 1 seconds, the ball is at height: 95.1 meters At 2 seconds, the ball is at height: 80.4 meters At 3 seconds, the ball is at height: 55.9 meters At 4 seconds, the ball is at height: 21.599999999999994 meters At 5 seconds, the ball is on the ground. ``` Same output as chapter 4's version for a 100-meter tower, but try 500, or 2: the program now *notices* the landing instead of being told when it is, which is this chapter's whole thesis in one diff. The loop is a `loop`-with-`break` rather than a `while`, so the landing line itself still prints (check the condition *after* reporting, not before); a `while ball_height(...) > 0.0` would stop one line early. Why not a `for`? Because the number of iterations is exactly what we don't know anymore. (The digit eruption at t = 4 is still chapter 4's rounding error; chapter 5 gave you `{:.1}` if it offends you.)
**Question #5** Write `is_prime(x: u32) -> bool` (a prime is a number ≥ 2 divisible only by 1 and itself), then use it to print every prime up to 30. Checking divisibility by every number from 2 to x−1 is fine for full credit. Extra credit: it's enough to check odd divisors whose *square* doesn't exceed x (any divisor pair has one member at or below the square root). Use `test * test <= x` so you don't need a square-root function.
Show solution Full credit: ```rust fn is_prime(x: u32) -> bool { if x < 2 { return false; } for divisor in 2..x { if x % divisor == 0 { return false; } } true } fn main() { for n in 1..=30 { if is_prime(n) { print!("{n} "); } } println!(); } ``` ``` 2 3 5 7 11 13 17 19 23 29 ``` Early-return style at work: each divisor found is an immediate `false`, and surviving the whole loop *is* the proof of primality (note 2 survives trivially: the range `2..2` is empty). Extra credit, same `main`: ```rust fn is_prime(x: u32) -> bool { if x < 2 { return false; } if x % 2 == 0 { return x == 2; } let mut test = 3; while test * test <= x { if x % test == 0 { return false; } test += 2; } true } ``` Evens are dispatched in one line (only 2 among them is prime), then only odd divisors are tried, and only while `test * test <= x`. For x = 29: tests 3 and 5, done; the naive version tried 27 divisors. The square trick matters more than it looks: for a ten-digit number, it's the difference between ~50,000 checks and ~5,000,000,000. A `while` loop, note, because the stopping condition (`test * test <= x`) isn't a simple range of values.
That's control flow: your programs now branch, repeat, recurse, quit early, and roll dice. Every value in them still obeys one unexamined rule, though: it lives exactly as long as its scope and vanishes at a closing brace. Chapter 8 finally asks the question this course exists to answer gently: who *owns* the data, and what does it mean to give it away? See you there.