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

Append a copy of a vector's first element, then return the sum of the updated vector. An empty vector stays empty and returns zero.

pub fn append_first(xs: &mut Vec<i64>) -> i64
Input Updated vector Return value
[] [] 0
[5] [5, 5] 10
[1, 2, 3] [1, 2, 3, 1] 7
[-4, 1] [-4, 1, -4] -7

Preserve all existing elements in order. For a nonempty vector, append exactly one element, even if the first element is zero. Both the return value and the updated vector are checked.

Work with inputs whose running sum, including the appended element, fits in i64. This exercise does not define an overflow result. Solve it without cloning the vector or using interior mutability; the element you need is Copy.

Why the starter gets E0502

xs.first() returns Option<&i64>. The starter keeps that reference in first, calls xs.push(*first), then reads *first again to update total.

The later read keeps the shared borrow active across push, which needs exclusive access to the vector. Rust reports E0502 because the borrows conflict. A push may replace the vector's allocation, so a reference into the previous allocation could become invalid. Spare capacity does not make the conflicting borrows acceptable to the compiler.

Copy the element before mutating

Keep an i64 value instead of a reference into the vector. In the pattern Some(&first), the & matches the reference and first receives a copy of the integer. Another spelling is xs.first().copied().

After the read, that integer has no borrow tying it to the vector. You can append it and then sum the updated vector. Handle the empty case without indexing xs[0], which would panic on an empty vector.

Here is a complete example of the same approach with a different mutation:

fn clear_and_keep_first(xs: &mut Vec<i64>) -> Option<i64> {
    let first = xs.first().copied();
    xs.clear();
    first
}

fn main() {
    let mut xs = vec![7, 8];
    assert_eq!(clear_and_keep_first(&mut xs), Some(7));
    assert!(xs.is_empty());
    assert_eq!(clear_and_keep_first(&mut xs), None);
}

Other ways to resolve a borrow conflict

If you need a reference rather than a Copy value, check whether its last use can happen before the mutation. Many borrows end at their last required use, without needing a new block. A returned reference or a destructor that uses borrowed data can keep a borrow active longer.

For more involved changes, first compute owned results such as indices or replacement values, then mutate the collection. Choose a collection method that fits the task when possible: retain, for example, can remove unwanted elements without collecting indices and repeatedly shifting the vector.

Cloning is useful when you actually need independently owned data. Types such as RefCell support mutation through shared access with runtime borrow checks; conflicting borrow/borrow_mut calls panic, while try_borrow variants return errors. Neither approach is needed to read one Copy element.

Submissions must compile, pass the output checks and pass Clippy. The output checks do not inspect which borrowing strategy your implementation uses.