Skip to content

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

Medium Primitives

The owner is frozen too: E0503, E0505, E0506

You are given a type and a function. The function does not compile.

pub struct Account {
    pub id: i64,
    pub balance: i64,
    pub history: Vec<i64>,
}

pub fn settle(mut a: Account, deltas: &[i64]) -> (i64, Vec<i64>)

settle takes ownership of an account. For each delta in order it adds the delta to balance and pushes the new balance onto history. It then returns (final balance, the history vector itself) — moved out of the account, not copied.

balance 100, history [],     deltas []        -> (100, [])
balance   0, history [],     deltas [-5,-10]  -> (-15, [-5,-15])
balance  10, history [10],   deltas [5]       -> (15,  [10,15])

The model you are about to lose

Most people arrive with an implicit hierarchy: the owner is in charge, and lending something out is a courtesy that can be revoked. Under that model, if a &a exists and the owner wants to reassign a, the owner wins.

Rust does not work that way, and these three error codes are how it tells you. Read the Reference’s phrasing from item 3.3 once more: a borrowed place “may not be mutated”; an exclusively borrowed place “may not be accessed in any way”. There is no exception carved out for the owner. A loan freezes the owner too.

Once that clicks, most of the remaining confusion in this track evaporates, because every other error is a special case of it.

The three codes, and the vocabulary they use

The diagnostics distinguish three ways of touching a place. Learning the words makes the messages readable.

E0506 — cannot assign to x because it is borrowed. You assigned. A shared loan exists, and you wrote to the place through the owner.

let mut n = 1;
let r = &n;
n = 2;              // E0506
println!("{r}");

E0505 — cannot move out of x because it is borrowed. You moved. The place is loaned and you tried to give the value away — which would leave the reference pointing at nothing.

let v = vec![1];
let r = &v;
let w = v;          // E0505
println!("{r:?}");

E0503 — cannot use x because it was mutably borrowed. You used — even just read. An exclusive loan exists, so every other route to the place is closed, reading included.

let mut n = 1;
let r = &mut n;
let copy = n;       // E0503
*r += 1;

Assign, move, use. Three verbs, three codes. When you see one, the first question is always the same: which loan is still alive, and why?

Which one the starter hits

The starter finishes the loop cleanly and then does this:

let acct = &a;             // (1) a shared loan of the whole struct
let history = a.history;   // (2) move a field out of a borrowed value -> E0505
(acct.balance, history)    // (3) "borrow later used here"

Same three-span reading as always. The loan at (1) is alive at (2) because of (3). Moving a.history out would leave acct pointing at a partially-emptied Account, so the compiler refuses.

And notice — this is the point of the exercise — that a is the function’s own local. It owns the account outright. Ownership does not help. The loan is what matters.

Why the struct contains a Vec

Account holds a Vec<i64>, so it is not Copy and never can be — Vec owns a heap allocation and duplicating it has to be an explicit, costly act. If every field were an integer, the struct would be Copy, let history = a.history; would be a copy rather than a move, and E0505 would never fire.

That is worth filing away: whether a line is a move or a copy is a property of the type, not of the syntax. The same let x = y; is free and harmless for an i64 and a transfer of ownership for a Vec. This is the single biggest source of “why does this work here and not there” in early Rust.

The fix

Do not create the loan. You need two values out of a: a Copy integer and an owned Vec. Take them directly. A tuple expression is evaluated left to right, so reading a.balance and then moving a.history in one expression is fine — the read has finished before the move happens.

Cloning the history to dodge the move would compile and would be wrong: the function’s whole contract is that the vector is handed over, and .clone() is withdrawn in this track anyway.

Remember the grade is compile + tests + clippy -D warnings.

Loading visualization…