Build the mental model
The error patterns encountered in Lesson 6 (ownership move), Lesson 7 (the borrow rule: one mutable OR many immutable), and Lesson 15 (lifetimes) are not meant to be recited as theory here — this exercise practices the debugging skill of spotting and fixing those symptoms yourself in real broken code. Rust's compiler error messages tend to be more helpful than in most languages — "cannot borrow as mutable because it is also borrowed as immutable" (a Lesson 7 rule violation), "value borrowed here after move" (reusing a value after Lesson 6's move), "missing lifetime specifier" (a Lesson 15 lifetime annotation is needed) — each clearly points at which rule was broken, so carefully reading the error message should be the first step in finding a fix. Most fixes come down to choosing one of three patterns: (1) replace an ownership move with a borrow (`&`), (2) call `.clone()` for an explicit deep copy, or (3) shrink a scope so conflicting borrows no longer overlap — each fix has its own tradeoff (performance cost versus code complexity) worth understanding. Rather than seeing borrow-checker errors as an annoyance, it helps to reframe them as catching, at compile time, exactly the kind of bug that would otherwise surface as a production runtime crash.
Connect it to a real scenario
Take three real-world scenarios from the content analyzer codebase: (1) code calling `fn print_and_return(s: String) -> String { println!("{s}"); s }` and then trying to reuse the original variable — the move error is fixed by switching to a `&str` parameter or adding `.clone()`. (2) code trying to mutate a `Vec<String>` from inside its own loop, `for item in &vec { vec.push(...) }` — the iteration itself is an immutable borrow, so push cannot happen inside that loop; it needs to switch to pushing after the loop finishes. (3) code with a missing lifetime annotation, `fn shortest<'a>(a: &str, b: &'a str) -> &'a str` — needs distinct `'a`/`'b` lifetimes, or both parameters aligned to share one lifetime.
Try the working example
// Snippet 1: fix the move error
fn print_and_return(s: String) -> String {
println!("{s}");
s
}
fn main() {
let title = String::from("Ownership");
let _ = print_and_return(title);
// println!("{title}"); // does not compile: value moved
// Snippet 2: fix the mutable-borrow-while-iterating error
let mut tags = vec![String::from("rust")];
// for t in &tags { tags.push(t.clone()); } // does not compile
// Snippet 3: fix the missing lifetime relationship
// fn shortest(a: &str, b: &str) -> &str { if a.len() < b.len() { a } else { b } }
}You fix all three snippets to compile, and explain in one line each which rule was broken and how.5-minute try-it
Fix all three snippets above so they compile — for each one, write a sentence on why you chose that particular fix (clone vs borrow vs shrinking scope).
One important caution
Trying to fix a borrow-checker error by blanket-adding `.clone()` everywhere without reading the error message — it may compile, but adds unnecessary performance cost without understanding the actual root cause.
Trying to bypass the borrow checker with an `unsafe` block instead of shrinking scope, for an error caused by mutating a collection from inside its own loop (`vec.push()`) — this breaks Rust's core safety guarantee and must be avoided.
The Rust Programming Language — References and Borrowing — Rust