Vec::extract_if¶
Level: reference · for working programmers
One line: retain's mirror image: it yields the elements it removes.
pub fn extract_if<F, R>(&mut self, range: R, filter: F) -> ExtractIf<'_, T, F, A>
where
F: FnMut(&mut T) -> bool,
R: RangeBounds<usize>,
Stable since 1.87.0.
Same &mut T predicate as retain_mut, but the removed elements come back as an iterator instead of being dropped. That makes in-place partitioning a single call with no second allocation for the keepers.
It takes a range as well as a predicate, so it can be confined to part of the vector. Everything outside the range is untouched.
It is lazy, and that is the trap. Dropping the iterator before consuming it removes only the elements it actually reached — the opposite of drain, which removes its whole range whether you consume it or not. Consume it fully (for _ in …, .collect(), .count()) unless partial removal is what you meant.
Panics if the range is out of bounds.
Stable since 1.87.0, under the feature name extract_if — it spent years unstable, so answers written before then reach for a retain plus a separate collecting pass instead.
Example¶
vec_extract_if.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.
fn main() {
// retain's mirror image: it yields the elements it removes.
let mut v = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<i32> = v.extract_if(.., |n| *n % 2 == 0).collect();
println!("extracted {evens:?} left {v:?}");
// A range restricts where it looks; everything outside is untouched.
let mut v = vec![0, 1, 2, 3, 4, 5];
let taken: Vec<i32> = v.extract_if(1..4, |n| *n % 2 == 1).collect();
println!("odds within 1..4: {taken:?} left {v:?}");
// Partitioning in place, with no second allocation for the keepers.
let mut jobs = vec!["ok:1", "err:2", "ok:3", "err:4"];
let failed: Vec<&str> = jobs.extract_if(.., |j| j.starts_with("err")).collect();
println!("failed {failed:?} remaining {jobs:?}");
// The predicate takes &mut T, like retain_mut.
let mut v = vec![1, 2, 3];
let big: Vec<i32> = v.extract_if(.., |n| { *n *= 10; *n > 15 }).collect();
println!("mutated then split: took {big:?}, left {v:?}");
// It is LAZY. Drop it without consuming and only the elements it reached
// are removed — which is the difference from retain, and a real trap.
let mut v = vec![1, 2, 3, 4];
let mut it = v.extract_if(.., |n| *n % 2 == 1);
it.next();
drop(it);
println!("stopped after the first match: {v:?}");
// Consuming it fully is the usual intent; `for` does that.
let mut v = vec![1, 2, 3, 4];
for _ in v.extract_if(.., |n| *n % 2 == 1) {}
println!("fully consumed: {v:?}");
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let caught = std::panic::catch_unwind(|| {
let mut v = vec![1, 2];
let _: Vec<i32> = v.extract_if(0..9, |_| true).collect();
});
std::panic::set_hook(hook);
println!("out-of-range range panicked: {}", caught.is_err());
}
Verified output of vec_extract_if.rs — regenerated by tools/run_examples.py, never hand-typed.
extracted [2, 4, 6] left [1, 3, 5]
odds within 1..4: [1, 3] left [0, 2, 4, 5]
failed ["err:2", "err:4"] remaining ["ok:1", "ok:3"]
mutated then split: took [20, 30], left [10]
stopped after the first match: [2, 3, 4]
fully consumed: [2, 4]
out-of-range range panicked: true
See also¶
Vec::retain— the same test, dropping what it removesVec::retain_mut— the same predicate signatureVec::drain— by range instead of by predicate, and eagerVec::split_off— partitioning at an index instead