9.xChapter 9 summary and quiz

Last updated June 12, 2026

Chapter 8 taught Rust's strictest idea; this chapter taught the idea that makes the strictness livable. Quick review, and then the quiz makes you use all of it at once.

Quick review

A reference is a value that refers to another value without owning it; creating one is called borrowing (9.1). The & appears on both sides of an agreement: &message at the call site lends, text: &String in the signature receives. A reference owns nothing, so dropping one frees nothing, and the borrowed value's one true owner carries on untouched. Plain & references are read-only, and any number may exist at once.

A mutable reference, spelled &mut, is a loan with writing permission (9.2). Mutation is written down three times (a mut binding, &mut at the call site, &mut T in the signature), which is why read_line(&mut name) looks the way it has since chapter 1. The borrow checker enforces the chapter's two rules: at any given time a value has either one mutable reference or any number of immutable ones, and references must always be valid. Borrows last from creation to last use, not to the closing brace, so reordering a few lines is a legitimate and common fix. The four refusals you'll meet most (9.3): E0596 (borrowing &mut from a non-mut binding), E0502 (writer while readers live), E0382 (use after move, usually cured with & rather than .clone()), and E0506 (assigning over a borrowed value).

Parameter design is a four-row table (9.4): Copy types by value, &T to read, &mut T to modify in place, owned T to keep or consume. Results travel in return values, not &mut out-parameters. A dangling reference, one that outlives its value, is rule 2's forbidden object (9.5): a function can return a reference only if it borrows from something living outside the call, which is why returning a view of a parameter compiles and returning a view of a local is refused at the signature. A lifetime is the stretch of program during which a value is alive; 'static, the whole-program lifetime of string literals, is the only one you'll spell until chapter 17.

A slice is a reference with a range: &temperatures[1..4] borrows a view of part of a sequence, of type &[i32], an address plus a length (9.6). Functions take slices rather than arrays for the same reason text functions take &str rather than &String, and that reason is now mechanical: deref coercion and its sibling conversions shrink an owner-shaped argument into the view a parameter asks for (9.7). A string slice is precisely that machinery aimed at text. Carve with byte indexes at character boundaries or Rust panics rather than splits a char; views found by scanning the text itself are always safe.

The chapter's thesis, witnessed in transcript form: the index version of first_word shipped a stale-bookkeeping bug, and the slice version made the same bug fail to compile. The borrow checker isn't only guarding memory. It guards the connection between your data and the facts you derive from it.

Quiz time

Question #1

From memory: state the two rules of references, and name the part of the compiler that enforces them.

Show solution

At any given time, a value can have either one mutable reference or any number of immutable references; and references must always be valid. The enforcer is the borrow checker. If you can also explain why each rule exists (readers must never watch a value change or vanish mid-look; a reference must never outlive its value), the chapter did its job.

Question #2

Compiles or not? For each failure, name the error code.

a)

fn main() {
    let recipe = String::from("toast");
    let a = &recipe;
    let b = &recipe;
    println!("{a} and {b}");
}

b)

fn main() {
    let mut recipe = String::from("toast");
    let a = &recipe;
    recipe.push_str(" with jam");
    println!("{a}");
}

c)

fn main() {
    let recipe = String::from("toast");
    let copy = recipe;
    println!("{recipe}");
}

d)

fn main() {
    let mut recipe = String::from("toast");
    let a = &mut recipe;
    a.push_str(" with jam");
    let b = &mut recipe;
    b.push_str(" and tea");
    println!("{recipe}");
}
Show solution

a) Compiles: any number of readers, toast and toast.

b) Refused, E0502: a is a live reader (used in the println!) when push_str needs a writable loan.

c) Refused, E0382: chapter 8's move, no references involved at all. let copy = recipe; ends recipe.

d) Compiles, printing toast with jam and tea. Two mutable borrows, but taking turns: a's last use is its push_str, so it's gone before b is created. One writer at a time, not one writer per lifetime of the value.

Question #3

Match each snippet to its error code (E0382, E0499, E0502, E0506, E0596). One code per snippet, no compiler.

a)

let mut s = String::from("hi");
let r1 = &mut s;
let r2 = &mut s;
println!("{r1} {r2}");

b)

let s = String::from("hi");
let r = &mut s;
r.push_str("!");

c)

let mut s = String::from("hi");
let r = &s;
s = String::from("bye");
println!("{r}");

d)

let s = String::from("hi");
let t = s;
println!("{s}");

e)

let mut s = String::from("hi");
let r = &s;
s.push_str("!");
println!("{r}");
Show solution

a) E0499, two simultaneous writers (the one combination the clinic lesson's patients didn't cover, and the code 9.2 introduced). b) E0596, writable loan against a non-mut binding. c) E0506, assigning over a borrowed value. d) E0382, use after move. e) E0502, writer while a reader lives.

Question #4

The capstone. Write a program that reads a line of text, prints its first word, then prints that word shouted with excitement added. Requirements: a first_word(s: &str) -> &str (yours from lesson 9.7, or rewrite it), a shouted(text: &str) -> String, and an add_excitement(text: &mut String) that appends !!!. Sample run, typing go rust go:

first word: go
GO!!!

Design check before you peek: which of your functions borrow, which one needs a writable loan, and which value in main owns heap text?

Show solution
use std::io;

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i] == b' ' {
            return &s[..i];
        }
        i += 1;
    }
    s
}

fn shouted(text: &str) -> String {
    text.to_uppercase()
}

fn add_excitement(text: &mut String) {
    text.push_str("!!!");
}

fn main() {
    let mut input = String::new();
    io::stdin().read_line(&mut input).expect("failed to read line");
    let phrase = input.trim();

    let word = first_word(phrase);
    println!("first word: {word}");

    let mut cheer = shouted(word);
    add_excitement(&mut cheer);
    println!("{cheer}");
}

Design notes, with 9.4's table doing the deciding. Two owners live in main: input (the read buffer; read_line takes the program's first &mut) and cheer (built by shouted, an owned return per 9.5: the function creates new text, so a view is impossible). Everything else is borrowed: phrase is trim's view into input, word is first_word's view into that view, and both functions that only read (first_word, shouted) take &str, so they'd accept literals just as happily. add_excitement is the legitimate &mut case: modifying a value the caller keeps, with the consent visible at the call site. And the borrows take turns: word's last use is the shouted call, after which input's readers are done; nothing overlaps, nothing is cloned, and the only text ever copied is the uppercase version, which had to be new.

If your version cloned somewhere, or a function took String and you had to give one back: those compile too, and chapter 8 explains what they cost. The ownership-and-borrowing way is the one where the signatures alone tell the story.

Eight chapters of String and numbers were the price of admission. With ownership (chapter 8) and borrowing (this chapter) in hand, you can finally be trusted with the keys to the type system: chapter 10 lets you define types of your own, and the &self in every method you'll ever write is this chapter wearing a new coat.