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.