We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 7 of 24
E0499: cannot borrow as mutable more than once
pub fn apply_swaps(xs: &mut [i64], swaps: &[[usize; 2]]) -> usize
swaps is a list of index pairs [i, j]. Process the pairs in the order
given. For each pair:
- If
iorj(or both) is not a valid index intoxs, that is, if it is greater than or equal toxs.len(), leavexsunchanged and count the pair as skipped. - Otherwise exchange the values at
xs[i]andxs[j].
Return the number of skipped pairs. The function changes xs in place; the
test harness prints the final xs and the returned count. Each swap sees the
result of the swaps before it, so the same pair applied twice undoes itself.
An empty swaps list returns 0 and leaves xs alone. Any usize value can
appear as an index, including very large ones; they are simply out of range.
The values in xs are only moved, never added or compared, so overflow
cannot happen.
xs swaps final xs skipped
[1,2,3] [[0,2]] [3,2,1] 0
[1,2,3] [[1,1]] [1,2,3] 0 (i == j: in range, so not skipped)
[1,2,3] [[0,5]] [1,2,3] 1 (5 is out of range)
[] [[0,0]] [] 1 (an empty slice has no valid index)
The second row needs care, and a test checks it. A pair such as [1, 1]
uses a valid index twice. Swapping an element with itself changes nothing,
but it is not a skip.
Why the starter code does not compile
The starter checks the bounds and then tries to take two mutable references:
let a = &mut xs[i];
let b = &mut xs[j]; // error[E0499]: cannot borrow `xs[_]` as mutable
std::mem::swap(a, b); // more than once at a time
A mutable reference (&mut T) is an exclusive borrow: while it is in use, no
other reference may reach the same data. Error E0499 means the compiler found
two mutable borrows that might overlap and are both still in use.
For a slice, xs[i] is built into the language rather than a call to a
trait method. The xs[_] in the error message is how the compiler names "some
element of xs": the borrow checker does not track index values when it
decides whether two borrows overlap. It treats any two element borrows of the
same slice as possibly the same element. That holds even for constant indices,
so &mut xs[0] and &mut xs[1] together are rejected with the same error.
(Two different struct fields, such as &mut p.a and &mut p.b, are accepted,
because field names are known at compile time.)
In this exercise the compiler is right to be cautious: i == j really does
happen, and two live &mut references to the same element are exactly what
Rust forbids. Moving the two lines around does not help. What does help is a
standard library function that checks the indices at run time and only then
gives you references it knows are separate.
get_disjoint_mut
Slices have a method get_disjoint_mut, stable since Rust 1.86 (it was
called get_many_mut while unstable). Simplified to plain usize indices,
its use looks like this:
match xs.get_disjoint_mut([i, j]) {
Ok([a, b]) => std::mem::swap(a, b), // a and b point at different elements
Err(e) => { /* e is a GetDisjointMutError */ }
}
You give it an array of indices and get back either an array of mutable
references, one per index, or an error. (The real signature is generic: it
also accepts arrays of ranges, which return mutable sub-slices.) The error type,
std::slice::GetDisjointMutError, has two variants:
IndexOutOfBounds: at least one index is not less than the slice length.OverlappingIndices: two of the requested positions overlap. For single indices this means two of them are equal.
These are the two conditions the compiler could not rule out on its own. The
method performs those checks at run time, and its Result makes the caller
decide what to do when they fail. "Disjoint" means "sharing no elements".
In this problem the bounds are checked first, so an Err after that point can
only be OverlappingIndices, which means i == j. That pair is a no-op: do
nothing, and do not count it as skipped.
There is also get_disjoint_unchecked_mut, an unsafe method that skips the
checks and leaves it to the caller to guarantee them. Calling it with equal or
out-of-range indices is undefined behavior. Use the checked method here.
The shorter route: slice::swap
Slices have had a swap method since Rust 1.0:
xs.swap(i, j);
It panics if either index is out of range and leaves the slice unchanged when
i == j. Because you check the bounds before calling it, it never panics in
this exercise, and it is the simplest correct answer.
If you instead write the swap by hand with a temporary
(let t = xs[i]; xs[i] = xs[j]; xs[j] = t;), it compiles, because each line
borrows xs only briefly and copies an i64. Clippy reports it as
manual_swap and suggests xs.swap(i, j). If you forget the temporary and
write xs[i] = xs[j]; xs[j] = xs[i];, both elements end up with the old
value of xs[j], and Clippy reports almost_swapped.
swap works because a single method performs both accesses inside itself.
When you need to do something other than exchange two elements, for example
add one element's value to another through two references, swap does not
apply and get_disjoint_mut does. Either approach is accepted here.
Grading
Your code must compile, pass the tests, and pass cargo clippy -- -D warnings
(every Clippy warning is treated as an error). One lint to watch for: a loop
written as for k in 0..swaps.len() that only uses k to index swaps
triggers needless_range_loop. Loop over the pairs directly with
for s in swaps (or for &[i, j] in swaps).
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.