3.xChapter 3 summary and quiz

Last updated June 11, 2026

Chapter 3 in one breath, then the quiz, which for this chapter means: here are some broken programs, go be the process.

Quick review

A syntax error breaks the grammar and dies at compile time. A semantic error (logic error) compiles and does the wrong thing. Rust reclassifies much of the traditional semantic-error category into compile-time errors, and catches another slice at runtime as a panic: an immediate, honest stop with a file-and-line report (dividing by a zero variable, a failing expect). What remains is the silent wrong answer, which is yours.

Debugging follows six steps: reproduce, locate, understand, plan, fix, retest (including the neighbors). Reproduce before theorizing; fix only what you understand; a vanished symptom is evidence, not proof.

Locating is search-space management. Inspect what's small and recent (the bug is usually in the last thing you touched); when staring fails, switch to running experiments: check values at the seams between functions, halve the territory with each observation, comment chunks out to test involvement, and change one thing per experiment, undoing as you go.

Programs have two output streams: stdout (println!, the product) and stderr (eprintln!, the commentary), and redirection (>) separates them. The dbg! macro is purpose-built instrumentation: it prints [file:line:column] expression = value to stderr and returns the value, so it wraps any expression in place. Probes are temporary; sweep them before calling work done.

A debugger pauses a live program: breakpoints choose where, stepping (over, into, out) moves in controlled doses, watching reads variables, and the call stack shows the chain of calls in progress, top to main. Use the dev profile, since release builds shed the debug info and rearrange the code.

And the prevention habits, cheaper than any hunt: build in small compiled steps, keep functions small and single-jobbed, never mix cleanup with behavior changes, ask what the worst caller could do, heed warnings and Clippy, and (chapter 14 trailer) write what "correct" means as runnable #[test] functions.

Quiz time

Question #1

A user reports this program always answers 0 plus the second number, whatever they type first. Find the bug, name which step-2 evidence would have exposed it fastest, and fix it.

fn main() {
    let x = 0;
    read_number();
    let y = read_number();
    println!("{} + {} is {}", x, y, x + y);
}

fn read_number() -> i32 {
    println!("Enter a whole number:");
    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 whole number")
}
Show solution

The first read_number() call's return value is discarded: the statement runs the prompt-and-read, then throws the result away, and x stays the 0 it was initialized to. The fix:

    let x = read_number();
    let y = read_number();

Fastest evidence: dbg!(x) (or a breakpoint) just before the println!, showing x = 0 no matter what was typed; the seam check immediately clears read_number's internals and points at the wiring in main. (The program even prompts twice and uses only one answer, a symptom visible with no tools at all: it asks, then ignores you, like a waiter.)

Question #2

This one compiles with a warning and then dies at runtime. Explain the crash, find the bug, and say what the warning was trying to tell you.

fn main() {
    let mut x = read_number();
    let y = 0;
    x = read_number();
    println!("{} / {} is {}", x, y, x / y);
}

fn read_number() -> i32 {
    println!("Enter a whole number:");
    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 whole number")
}
Enter a whole number:
8
Enter a whole number:
2
thread 'main' (1127017) panicked at src/main.rs:5:37:
attempt to divide by zero
Show solution

The second input was meant to become y, but the line assigns it to x again (a copy-paste classic). y keeps its placeholder 0, and the division panics. The fix is the intended wiring, which also dissolves the mut and the placeholder:

    let x = read_number();
    let y = read_number();

The warning ("value assigned to x is never read", on the first read) was the whole story in advance: a value was computed, stored, and overwritten before any use, which is precisely the bug. Warnings first, then tools.

Question #3

No code to run for this one; predict from your mental model. The program below is paused by a breakpoint on the marked line. What does the call stack panel show, top to bottom?

fn main() {
    outer();
}

fn outer() {
    helper();
}

fn helper() {
    let x = 1; // breakpoint here
    println!("{x}");
}
Show solution

Top to bottom: helper, outer, main. The top entry is where execution is paused; each entry below is a function mid-call, waiting for the one above it to return; main anchors the bottom of every Rust stack.

That's the on-ramp milestone complete: tools installed, language basics, functions, and now debugging. Chapter 4 starts the language's real depth: what those i32s have been all along, the rest of their family, and the first appearance of if. The programs get noticeably more interesting from here.