Skip to content

← Ownership II: Borrowing and the Borrow Checker step 6 of 24

Easy Primitives

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…