139. HashMap::extract_if — Drain Matching Entries Without Losing Them
HashMap::retain discards what it removes. When you actually want those values — to log them, ship them, or move them elsewhere — you used to clone the keys and double-walk the map. extract_if, stable since Rust 1.88, hands them back as an iterator.
retain throws the babies out with the bathwater
retain is the natural “filter a map in place” call, but its return type is (). The entries it kicks out vanish:
| |
If you wanted to log the expired sessions, notify their owners, or forward them to another map, retain isn’t enough. The textbook workaround is collect-then-remove:
| |
Two passes, a throwaway Vec<K>, and a hard K: Clone requirement just so the borrow checker stops yelling.
extract_if is one pass and gives the values back
HashMap::extract_if(pred) returns an iterator that yields (K, V) for every entry whose predicate fires true, removing them from the map as it goes:
| |
No clone, no temporary Vec, no second walk. The removed entries land in whatever collection you choose — another HashMap, a Vec<(K, V)>, a channel, a log line. HashMap order isn’t guaranteed, but neither was it before.
The predicate gets &mut V, so it can edit survivors
The closure signature is FnMut(&K, &mut V) -> bool. Return true to extract, false to keep — and because you’ve got &mut V in hand either way, you can update entries you choose to leave behind:
| |
One traversal, three jobs: filter, remove-and-collect, and patch in place.
Drop the iterator early and the rest stay
The iterator only removes entries it has visited. If you stop iterating — break, take(n), drop the iterator — anything not yet inspected stays in the map untouched:
| |
When to reach for it
Whenever you’d otherwise write the clone-keys-and-double-pass shuffle: expiring sessions, draining tasks that hit a deadline, partitioning a cache by tenant, moving “done” items into an archive map. HashSet::extract_if is the same idea for sets — predicate is FnMut(&T) -> bool. The mental rule: reach for retain when the discarded entries are truly garbage, and extract_if when they still have a job to do.