Frontend Labs
β‹―

13. πŸ”Ž Checkpoint: ownership and borrowing

Try each one before you open the answer.

1. Predict: does it compile? If not, which line fails?

// DOES NOT COMPILE
fn main() {
    let names = vec![String::from("a"), String::from("b")];
    for n in names {
        println!("{n}");
    }
    println!("{}", names.len());
}
Answer

The last line fails with E0382 (borrow of moved value). for n in names calls names.into_iter(), which consumes the vector, so each n is an owned String. Write for n in &names to iterate over &String and keep names usable.

2. Spot the bug:

// DOES NOT COMPILE
fn longest(words: &Vec<String>) -> &String {
    let mut best = &String::new();
    for w in words { if w.len() > best.len() { best = w; } }
    best
}
fn main() { println!("{}", longest(&vec!["hi".into()])); }
Answer

&String::new() creates a temporary owned by longest itself. Rust extends its life to the end of the function body, but no further. If words is empty, best still points at that temporary when the function returns, so the compiler rejects the best return with E0515 ("cannot return value referencing temporary value"). The signature promises a reference into words, and this path breaks that promise. Fix: start from words.first(), return Option<&str> so the empty case has an answer, and take &[String]:

fn longest(words: &[String]) -> Option<&str> {
    words.iter().map(String::as_str).max_by_key(|w| w.len())
}
fn main() { println!("{:?}", longest(&["hi".into(), "hello".into()])); }

(When several words share the maximum length, max_by_key returns the last one.)

3. Write it yourself: fn dedupe_sorted(v: &mut Vec<i32>) that removes duplicates from an unsorted vector in place, without cloning the vector.

One correct answer
fn dedupe_sorted(v: &mut Vec<i32>) {
    v.sort_unstable();
    v.dedup();
}
fn main() {
    let mut v = vec![3, 1, 3, 2, 1];
    dedupe_sorted(&mut v);
    println!("{v:?}"); // [1, 2, 3]
}

To keep the original order instead, use retain with a HashSet of values you have already seen: let mut seen = HashSet::new(); v.retain(|x| seen.insert(*x));.

4. Conceptual: why is let s2 = s1; a move for String but a copy for (i32, bool)?

Answer

String owns a heap buffer and implements Drop. Two bitwise copies would both free that buffer, so the language transfers ownership instead. (i32, bool) owns nothing and implements Copy, so a bitwise copy is a complete, independent value. The rule is: a type is Copy only if copying its bytes is enough to duplicate it.

5. Predict the output:

fn main() {
    let mut s = String::from("ab");
    let r = &mut s;
    r.push('c');
    s.push('d');
    println!("{s}");
}
Answer

abcd. It compiles because the mutable borrow r is last used at r.push('c'). After that line the borrow is over, so s can be used directly again (non-lexical lifetimes, Β§7.3). If you added println!("{r}") at the end, it would fail with E0499.


Generated from real-life-rust-guide.md Β· 8 Oct 2026 16:36