Skip to content

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

Medium Primitives

Accessing an owned value while it is borrowed

Repair the borrow error in settle. It owns an account, applies each balance change, and returns the final balance together with the history.

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

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

For each delta, add it to balance, then append the new balance to history. Preserve all existing history entries. Even a zero delta adds an entry. Empty deltas leave both the balance and history unchanged.

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

All intermediate sums in the test inputs fit in i64. The account ID does not affect settlement. Move the history vector into the result without cloning it; normal vector growth while appending is allowed.

Ownership does not cancel a borrow

Owning a value lets you lend access to it, but you must still respect that access while the borrow is needed. The starter borrows the entire account, moves its history, then reads through the earlier borrow:

let acct = &a;
let history = a.history; // E0505: the account is still borrowed.
(acct.balance, history)

This is an excerpt from the failing function. The final use of acct keeps the whole-account borrow active across the field move. Moving the field would leave the borrowed account partially moved.

The needed outputs are available directly from the owned account. Reading a.balance copies an i64; reading a.history by value moves a Vec<i64>. Removing the unnecessary whole-account reference allows both.

You could also copy the balance into a local variable before moving the history. The key is avoiding an overlapping whole-account borrow, not relying on a special tuple-expression rule.

Recognize the conflicting operation

These diagnostics describe related borrow conflicts:

Code Typical conflict
E0506 Assigning to a place while a borrow of it is still needed
E0505 Moving a value while it is still borrowed
E0503 Reading or otherwise using a place while a mutable borrow is still needed

When reading an error, find where the borrow starts, the conflicting operation, and the later use that keeps the borrow active. In this complete example, moving the read before the mutable borrow avoids the conflict:

fn main() {
    let mut number = 1;
    let previous = number;
    let borrowed = &mut number;
    *borrowed += 1;
    assert_eq!(previous, 1);
    assert_eq!(number, 2);
}

Moving let previous = number; between the creation and use of borrowed produces E0503.

Borrow restrictions concern the borrowed place and conflicting accesses, not every operation on the owner. For example, shared reads may coexist, and borrowing one struct field can leave a disjoint field accessible. Interior-mutability types provide separate rules for permitted mutation.

Copying a field and moving a field

i64 implements Copy; Vec<i64> does not. Moving a vector transfers ownership of its buffer instead of duplicating its elements. The account cannot implement Copy while it contains that vector.

A struct whose fields all implement Copy can derive or implement Copy, but does not acquire it automatically. Also, whether extracting a field copies it depends on the field's type, not on whether the enclosing struct implements Copy.

Cloning the history could produce the same output values, but would not meet this exercise's ownership-transfer requirement. Output tests alone cannot establish the absence of a clone.

A passing submission must compile, pass the tests, and pass clippy -D warnings.

Loading visualization…