We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 12 of 24
Remove matching values without invalidating iteration
Remove every occurrence of target from the existing vector and return the removed values. Preserve the order of the surviving elements.
pub fn compact(xs: &mut Vec<i64>, target: i64) -> Vec<i64>
The returned vector contains one entry for every removal, in original order. Since all removed values equal target, its length records how many matches were removed. The harness checks both this vector and the updated xs.
| Input xs | Target | Updated xs | Returned values |
|---|---|---|---|
[1, 2, 3] |
9 |
[1, 2, 3] |
[] |
[5, 5, 5] |
5 |
[] |
[5, 5, 5] |
[1, 7, 7, 2] |
7 |
[1, 2] |
[7, 7] |
[4, 1, 2, 4] |
4 |
[1, 2] |
[4, 4] |
[] |
0 |
[] |
[] |
Update xs in place; allocating the returned vector is expected. Values can be any i64, including negative values and the integer limits. No arithmetic is needed to decide whether a value matches.
Why the starter does not compile
The starter walks xs.iter() while calling xs.remove(i). The iterator holds a shared borrow of the vector's elements, while remove requires mutable access to the vector. Those borrows overlap, so the compiler reports E0502.
Removal shifts later elements to new positions. An iterator or reference into the old arrangement cannot simply be assumed to keep identifying the same value. Rust rejects this conflicting access before execution. That does not mean every such operation would cause a use-after-free, nor does the borrow checker prove that a replacement algorithm gives the right answer.
The adjacent-match trap
An indexed loop that increments i after every removal can compile and still
skip matches. For [1, 7, 7, 2], removing the first 7 shifts the second into its
position. Incrementing i then moves past it, leaving [1, 7, 2] and returning
only one 7.
A correctly written indexed loop can work: after removing an element, it must check the same index again. Repeated Vec::remove calls also shift the suffix repeatedly. A collection operation designed for filtering expresses this task more directly.
Extract and collect the matches
Vec::extract_if takes a range and a predicate. Use the full range .. and a
predicate that compares each element with target. It removes matching elements
and yields them; nonmatching elements stay in their original relative order.
The extraction iterator is lazy. Consume it completely to process the whole range and collect all removed values. Creating it and immediately dropping it does not remove all matches. If it is only partly consumed, unvisited elements remain in the vector.
This standalone example shows the distinction:
fn main() {
let mut values = vec![7, 1, 7, 2];
{
let mut removed = values.extract_if(.., |value| *value == 7);
assert_eq!(removed.next(), Some(7));
}
assert_eq!(values, [1, 7, 2]);
let rest: Vec<_> = values.extract_if(.., |value| *value == 7).collect();
assert_eq!(rest, [7]);
assert_eq!(values, [1, 2]);
}
See the Vec::extract_if documentation.
Other APIs solve related tasks. retain keeps values selected by its predicate but does not yield the removals; with these Copy values, its closure could record them separately. drain removes a positional range. swap_remove changes order by filling a removed slot with the last element, so it is unsuitable for this contract. extract_if is a direct fit because it both filters and yields.
Passing requires compilation, correct outputs and Clippy with warnings denied.
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.