Vec::pop_if got a deque-shaped sibling. As of Rust 1.93, VecDeque has pop_front_if and pop_back_if — conditional pops on either end without the peek-then-pop dance.
The problem
You want to remove an element from a VecDeque only when it matches a predicate. Before 1.93, you’d peek, match, then pop:
1
2
3
4
5
6
7
| use std::collections::VecDeque;
let mut queue: VecDeque<i32> = VecDeque::from([1, 2, 3, 4]);
if queue.front().is_some_and(|&x| x == 1) {
queue.pop_front();
}
|
Two lookups, two branches, one opportunity to desynchronize the check from the pop if you refactor the predicate later.
The fix
pop_front_if takes a closure, checks the front element against it, and pops it only if it matches. pop_back_if does the same on the other end.
1
2
3
4
5
6
7
8
9
10
11
12
| use std::collections::VecDeque;
let mut queue: VecDeque<i32> = VecDeque::from([1, 2, 3, 4]);
let popped = queue.pop_front_if(|x| *x == 1);
assert_eq!(popped, Some(1));
assert_eq!(queue, VecDeque::from([2, 3, 4]));
// Predicate doesn't match — nothing is popped.
let not_popped = queue.pop_front_if(|x| *x > 100);
assert_eq!(not_popped, None);
assert_eq!(queue, VecDeque::from([2, 3, 4]));
|
The return type is Option<T>: Some(value) if the predicate matched and the element was removed, None otherwise (including when the deque is empty).
One subtle detail worth noting — the closure receives &mut T, not &T. That means |&x| won’t type-check; use |x| *x == ... or destructure with |&mut x|. The extra flexibility lets you mutate the element in-place before deciding to pop it.
Draining a prefix
The pattern clicks when you pair it with a while let loop. Drain everything at the front that matches a condition, stop the moment it doesn’t:
1
2
3
4
5
6
7
8
| use std::collections::VecDeque;
let mut events: VecDeque<i32> = VecDeque::from([1, 2, 3, 10, 11, 12]);
// Drain the "small" prefix only.
while let Some(_) = events.pop_front_if(|x| *x < 10) {}
assert_eq!(events, VecDeque::from([10, 11, 12]));
|
No index tracking, no split_off, no collecting into a new deque.
Why both ends?
VecDeque is a double-ended ring buffer, so it’s natural to support the same idiom on both sides. Processing a priority queue from the back, trimming expired entries from the front, popping a sentinel only when it’s still there — all one method call each.
1
2
3
4
5
6
7
8
9
10
| use std::collections::VecDeque;
let mut log: VecDeque<&str> = VecDeque::from(["START", "a", "b", "c", "END"]);
let end = log.pop_back_if(|s| *s == "END");
let start = log.pop_front_if(|s| *s == "START");
assert_eq!(start, Some("START"));
assert_eq!(end, Some("END"));
assert_eq!(log, VecDeque::from(["a", "b", "c"]));
|
When to reach for it
Whenever the shape of your code is “peek, compare, pop.” That’s the tell. pop_front_if / pop_back_if collapse three steps into one atomic operation, and the Option<T> return makes it composable with while let, ?, and the rest of the Option toolbox.
Stabilized in Rust 1.93 — if your MSRV is recent enough, this is a free readability win.