We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 6 of 24
E0502: cannot borrow as mutable because also borrowed as immutable
pub fn append_first(xs: &mut Vec<i64>) -> i64
Append a copy of the vector’s current first element to the end, then return
the sum of the vector after the append. If the vector is empty, leave it
alone and return 0.
[] -> returns 0, stays []
[5] -> returns 10, becomes [5,5]
[1,2,3] -> returns 7, becomes [1,2,3,1]
[-4,1] -> returns -7, becomes [-4,1,-4]
Both the return value and the final vector are checked, so you cannot pass by computing the right number without actually appending.
The starter is a plausible first draft. It does not compile.
E0502, the error that makes people quit
error[E0502]: cannot borrow `*xs` as mutable because it is also borrowed as immutable
It is worth knowing that this is the wall. More people abandon Rust on E0502 than on anything else, because it appears in week one, it appears on code that is obviously correct, and the obvious fixes make it worse.
It stops being a wall the moment you have a checklist. Here is one. Work through it in order and stop at the first item that applies.
1. Copy the value out
If what you need from the borrowed place is a small Copy type — an integer,
a bool, a char, a usize length — do not hold a reference to it. Read it
into a local:
let first = xs[0]; // i64: Copy. The loan dies on this line.
The loan needed to perform that read begins and ends within the expression.
Afterwards first is an independent value that has no relationship to the
vector at all, and you may mutate the vector as much as you like.
This is the fix for the starter, and it is the fix for most beginner E0502s. It costs eight bytes.
2. Shorten the loan
If you cannot copy, look at the third span of the diagnostic — “borrow later used here” — and ask whether that use can move earlier, or disappear. A loan with no later use is dead and conflicts with nothing (item 3.5).
3. Compute, then mutate
Split the function into two phases. Phase one reads the collection and produces a plain description of what to do — a list of indices, a count, a set of new values. Phase two consumes that description and does the mutation, with no loans outstanding.
let doomed: Vec<usize> = xs.iter().enumerate()
.filter(|(_, v)| **v < 0).map(|(i, _)| i).collect();
for i in doomed.into_iter().rev() { xs.remove(i); }
This is the single most transferable habit in Rust. When a design keeps fighting the checker, it is usually because reading and writing are interleaved, and separating them is a better program independent of the compiler.
4. Clone — and when that is honest
Sometimes the data genuinely has to exist in two places and there is no way
around a copy. That is fine; it just has to be a decision rather than a
reflex. .clone() is withdrawn for this whole track precisely so you cannot
reach for it before trying 1 to 3. If you find yourself cloning an entire
collection to dodge a loan on one element, you skipped step 1.
5. Interior mutability — last resort
RefCell moves the aliasing check from compile time to run time. It buys you
shapes the checker cannot express, at the cost of a runtime counter and a
possible panic in production. Item 3.22 covers it properly. It is not a way to
avoid understanding steps 1 to 4.
Why the starter fails, precisely
let first = match xs.first() { Some(f) => f, None => return 0 };
let mut total: i64 = xs.iter().sum();
xs.push(*first); // <- mutable borrow: E0502
total += *first; // <- "immutable borrow later used here"
total
first is a &i64 pointing into the vector’s buffer. xs.push(...) may
reallocate that buffer, which would leave first dangling — which is exactly
the class of bug the rule exists to prevent. Notice that the compiler is not
being fussy: this program, compiled by a language without the rule, has a real
use-after-free in it whenever the push triggers a growth.
Read the three spans, apply step 1, and the whole thing collapses into three lines.
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.