Skip to content

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

Easy Primitives

&mut: exclusive access, and why mut appears twice

Implement three functions that edit the caller's collection:

pub fn double_all(xs: &mut [i64])
pub fn clamp_all(xs: &mut [i64], lo: i64, hi: i64)
pub fn drop_zeros(xs: &mut Vec<i64>)

The harness calls them in this order:

  1. Double every element.
  2. Replace values below lo with lo and values above hi with hi.
  3. Remove zeroes, preserving the order and duplicates of all remaining values.

Assume lo <= hi and that doubling each original element fits in i64. An empty collection stays empty. The clamp bounds themselves may be any i64 values satisfying their ordering constraint.

Input and bounds After doubling After clamping Final result
[1, -3, 7], -4 to 6 [2, -6, 14] [2, -4, 6] [2, -4, 6]
[0, 5], -100 to 100 [0, 10] [0, 10] [10]
[0, -2], 2 to 2 [0, -4] [2, 2] [2, 2]

Remove zeroes after clamping: a zero can become nonzero, and a nonzero value can become zero. Do not filter the original input first.

The starter leaves the first two functions as todo!(). Its drop_zeros tries to push into an immutable local vector. Repair or replace that implementation, and complete the two unfinished functions.

A mutable binding and a mutable reference do different jobs

let mut values = vec![1i64, 2, 3];
let view = &mut values;
view.push(4);
assert_eq!(view.as_slice(), &[1, 2, 3, 4]);

mut values allows this owned vector to be mutated. The &mut expression borrows it with exclusive access. The binding view does not need its own mut to modify the vector through that reference. It would need mut to be reassigned to another reference.

In the starter, keep owns a vector. Calling keep.push(...) needs a mutable borrow of that vector, so the missing mut on let keep produces E0596. Adding mut repairs that specific error. You can also simplify drop_zeros with Vec::retain, avoiding the temporary vector entirely.

Exclusive access lasts for the borrow

While a mutable borrow is used, conflicting access through the owner is forbidden. The borrow can end before the reference binding's enclosing scope ends:

let mut values = vec![1i64, 2];
let view = &mut values;
view.push(3); // Last use of view.
assert_eq!(values, vec![1, 2, 3]);

You can also temporarily reborrow through a mutable reference. During that reborrow, use the new reference rather than conflicting access through the original one. Exclusivity does not mean no other reference can ever be derived from it.

Slices edit elements; vectors can change length

&mut [i64] gives these functions access to a fixed range of elements. They can overwrite values without resizing the caller's collection. This lets double_all and clamp_all accept arrays, vector contents or part of either collection.

drop_zeros needs to change the vector's length, so it takes &mut Vec<i64>. retain keeps only entries satisfying a predicate and preserves their relative order.

When the harness passes &mut xs to the first two functions, Rust coerces the mutable vector reference to a mutable slice. The last function borrows the vector itself; ownership remains with the caller throughout.

Iterate with xs.iter_mut() to get a mutable reference to each element, then assign through *x. (*x).clamp(lo, hi) gives the clamped value. These operations express the task directly without index bookkeeping.

Submissions must compile, pass the cases and pass the Rust Clippy gate.