We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 4 of 24
How to read a borrow-check diagnostic
The function below is written for you. It does not compile. Your job is to read the error properly and fix it.
pub fn summarize(words: &mut Vec<String>) -> String
What it must do: push one new line onto words of the form "total=N", where
N is how many words there were before the push, and return the first
word as it was before the push — or "" if the vector was empty.
[] -> returns "", words become ["total=0"]
["a"] -> returns "a", words become ["a", "total=1"]
["a","bb","ccc"] -> returns "a", words become ["a","bb","ccc","total=3"]
Note the empty case: it must not panic. &words[0] on an empty vector is a
panic, so any correct answer has to go through first() or get(0). (Clippy
will push you to first() if you reach for get(0) — that is the get_first
lint.)
The actual lesson: three spans
Here is what rustc says about the starter:
error[E0502]: cannot borrow `*words` as mutable because it is also borrowed as immutable
--> src/lib.rs:5:5
|
3 | let first = &words[0];
| ----- immutable borrow occurs here
4 |
5 | words.push(format!("total={}", words.len()));
| ^^^^^^^^^^ mutable borrow occurs here
...
7 | first.to_string()
| ----- immutable borrow later used here
Three spans. Beginners fixate on the one with the carets, because that is the one that looks like the error. It is the least useful of the three.
Span 1 — where the loan was created. let first = &words[0];. This is the
moment you asked for a view into the vector. Nothing is wrong here yet.
Span 2 — the conflicting action. words.push(...). This is where rustc
gave up: pushing needs exclusive access, and the loan from span 1 says
somebody else is looking. This is the line with the carets, and it is where
people start deleting code. Usually a mistake — the push is the thing you
actually wanted to do.
Span 3 — “borrow later used here”. first.to_string(). This is the
interesting one, and the one to attack. It is rustc telling you why the loan
from span 1 was still alive at span 2. Borrows in Rust end at their last use
(item 3.5 is about exactly that). If span 3 did not exist, the loan would have
been dead before span 2 and there would be no error at all.
So the reading order is 1, 3, 2: what did I borrow, why is it still alive, and only then, what did I collide with.
The fix follows from span 3
Once you know the problem is “span 3 keeps span 1 alive”, the fixes present themselves in a natural order:
- Take a copy of what you need, and drop the loan. You do not want a reference into the vector; you want the first word’s text. Get the text out first, then mutate freely. This is the right answer here.
- Move the mutation after the last use of the loan. Sometimes the two just need reordering.
- Narrow the loan. Borrow less: one field instead of the whole struct, or one element instead of the whole collection.
And one non-fix worth naming so you can refuse it: cloning the entire Vec to
dodge the error. It compiles, it is quadratic, and it tells a reviewer you did
not read span 3. In this track .clone() is withdrawn anyway — you have to
find the real fix. (Copying a single String‘s contents into a fresh
String is a different and much smaller thing; that one is honest here,
because the function’s return type genuinely is an owned String.)
A hint about how to get the text out without cloning the Vec
You need an owned String and you have an Option<&String>. One clean route
is to start with an empty String and push_str into it inside an
if let Some(w) = words.first() { ... } — the loan lives exactly as long as
that block and is dead by the time you push.
That is the shape to remember: compute what you need, end the loan, then mutate. It is the most transferable habit in the language, and item 3.6 drills it.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.