#137 May 13, 2026

137. Vec::retain_mut — Filter and Edit In Place, In One Pass

retain decides who stays, but its closure only sees &T — so when you also want to tweak the survivors, you end up making two passes. retain_mut collapses that into one.

The Problem

Imagine a Vec<Job> where each surviving job needs its retry counter bumped on the way through. With plain retain, the closure gets a shared reference, so the mutation has to happen separately:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct Job { id: u32, retries: u32, done: bool }

let mut jobs = vec![
    Job { id: 1, retries: 0, done: false },
    Job { id: 2, retries: 0, done: true  },
    Job { id: 3, retries: 0, done: false },
];

// Pass 1: bump the survivors.
for j in jobs.iter_mut().filter(|j| !j.done) {
    j.retries += 1;
}
// Pass 2: drop the finished ones.
jobs.retain(|j| !j.done);

Two passes, two intent-leaks, and the predicate is duplicated. Refactor one and forget to refactor the other — welcome to a subtle bug.

retain_mut: One Pass, Mutable Access

Vec::retain_mut gives the closure a &mut T. Edit the element and return whether to keep it — same call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct Job { id: u32, retries: u32, done: bool }

let mut jobs = vec![
    Job { id: 1, retries: 0, done: false },
    Job { id: 2, retries: 0, done: true  },
    Job { id: 3, retries: 0, done: false },
];

jobs.retain_mut(|j| {
    if j.done { return false; }
    j.retries += 1;
    true
});

assert_eq!(jobs.len(), 2);
assert_eq!(jobs[0].id, 1);
assert_eq!(jobs[0].retries, 1);
assert_eq!(jobs[1].id, 3);
assert_eq!(jobs[1].retries, 1);

One scan, no duplicated predicate, and the order of the kept elements is preserved.

The Sneaky Trick: Mutate-Then-Check

The mutation happens before the closure returns, so a retain_mut can also normalize values and then filter on the normalized form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let mut words = vec![
    String::from("  hello "),
    String::from("   "),
    String::from(" Rust"),
];

words.retain_mut(|s| {
    *s = s.trim().to_string();
    !s.is_empty()
});

assert_eq!(words, vec!["hello", "Rust"]);

Trim everything, then drop the blanks — without ever allocating a second Vec.

When to Reach for It

Use retain_mut whenever the keep/drop decision and an in-place edit travel together. Same goes for VecDeque::retain_mut and LinkedList::retain_mut — same shape, same payoff. If the closure is purely read-only, stick with retain and keep the intent narrow.

← Previous 136. LazyLock::force_mut — Mutate a Lazy Value Without Wrapping It in a Mutex Next → 138. iter::zip — Parallel Iteration Without the Method-Chain Dance