Collections

64. Iterator::unzip — Split Pairs into Separate Collections

Got an iterator of tuples and need two separate collections? Stop looping and pushing manually — unzip splits pairs into two collections in a single pass.

The problem

You have an iterator that yields pairs — maybe key-value tuples from a computation, or results from enumerate. You need two separate Vecs. The manual approach works, but it’s noisy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let pairs = vec![("Alice", 95), ("Bob", 87), ("Carol", 92)];

let mut names = Vec::new();
let mut scores = Vec::new();
for (name, score) in &pairs {
    names.push(*name);
    scores.push(*score);
}

assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
assert_eq!(scores, vec![95, 87, 92]);

Two mutable Vecs, a loop, manual pushes — all to split pairs apart.

The fix

Iterator::unzip does exactly this in one line:

1
2
3
4
5
6
let pairs = vec![("Alice", 95), ("Bob", 87), ("Carol", 92)];

let (names, scores): (Vec<&str>, Vec<i32>) = pairs.into_iter().unzip();

assert_eq!(names, vec!["Alice", "Bob", "Carol"]);
assert_eq!(scores, vec![95, 87, 92]);

The type annotation on the left tells Rust which collections to build. It works with any types that implement Default + Extend — so Vec, String, HashSet, and more.

Works great with enumerate

Need indices and values in separate collections?

1
2
3
4
5
6
let fruits = vec!["apple", "banana", "cherry"];

let (indices, items): (Vec<usize>, Vec<&&str>) = fruits.iter().enumerate().unzip();

assert_eq!(indices, vec![0, 1, 2]);
assert_eq!(items, vec![&"apple", &"banana", &"cherry"]);

Combine with map for transforms

Chain map before unzip to transform on the fly:

1
2
3
4
5
6
7
8
9
let data = vec![("temp_c", 20.0), ("temp_c", 35.0), ("temp_c", 0.0)];

let (labels, fahrenheit): (Vec<&str>, Vec<f64>) = data
    .into_iter()
    .map(|(label, c)| (label, c * 9.0 / 5.0 + 32.0))
    .unzip();

assert_eq!(labels, vec!["temp_c", "temp_c", "temp_c"]);
assert_eq!(fahrenheit, vec![68.0, 95.0, 32.0]);

Unzip into different collection types

Since unzip works with any Default + Extend types, you can collect into mixed collections:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::HashSet;

let entries = vec![("admin", "read"), ("admin", "write"), ("user", "read")];

let (roles, perms): (Vec<&str>, HashSet<&str>) = entries.into_iter().unzip();

assert_eq!(roles, vec!["admin", "admin", "user"]);
assert_eq!(perms.len(), 2); // "read" and "write", deduplicated
assert!(perms.contains("read"));
assert!(perms.contains("write"));

One side is a Vec, the other is a HashSetunzip doesn’t care, as long as both sides can extend themselves.

Whenever you’re about to write a loop that pushes into two collections, reach for unzip instead.

60. Iterator::partition — Split a Collection in Two

Need to split a collection into two groups based on a condition? Skip the manual loop — Iterator::partition does it in one call.

The manual way

Without partition, you’d loop and push into two separate vectors:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

let mut evens = Vec::new();
let mut odds = Vec::new();

for n in numbers {
    if n % 2 == 0 {
        evens.push(n);
    } else {
        odds.push(n);
    }
}

assert_eq!(evens, vec![2, 4, 6, 8]);
assert_eq!(odds, vec![1, 3, 5, 7]);

It works, but it’s a lot of ceremony for a simple split.

Enter partition

Iterator::partition collects into two collections in a single pass. Items where the predicate returns true go left, false goes right:

1
2
3
4
5
6
7
8
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8];

let (evens, odds): (Vec<_>, Vec<_>) = numbers
    .iter()
    .partition(|&&n| n % 2 == 0);

assert_eq!(evens, vec![&2, &4, &6, &8]);
assert_eq!(odds, vec![&1, &3, &5, &7]);

The type annotation (Vec<_>, Vec<_>) is required — Rust needs to know what collections to build. You can partition into any type that implements Default + Extend, not just Vec.

Owned values with into_iter

Use into_iter() when you want owned values instead of references:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let files = vec![
    "main.rs", "lib.rs", "test_utils.rs",
    "README.md", "CHANGELOG.md",
];

let (rust_files, other_files): (Vec<_>, Vec<_>) = files
    .into_iter()
    .partition(|f| f.ends_with(".rs"));

assert_eq!(rust_files, vec!["main.rs", "lib.rs", "test_utils.rs"]);
assert_eq!(other_files, vec!["README.md", "CHANGELOG.md"]);

A practical use: triaging results

partition pairs beautifully with Result to separate successes from failures:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let inputs = vec!["42", "not_a_number", "7", "oops", "13"];

let (oks, errs): (Vec<_>, Vec<_>) = inputs
    .iter()
    .map(|s| s.parse::<i32>())
    .partition(Result::is_ok);

let values: Vec<i32> = oks.into_iter().map(Result::unwrap).collect();
let failures: Vec<_> = errs.into_iter().map(Result::unwrap_err).collect();

assert_eq!(values, vec![42, 7, 13]);
assert_eq!(failures.len(), 2);

partition has been stable since Rust 1.0 — one of those hidden gems that’s been there all along. Anytime you reach for a loop to split items into two buckets, reach for partition instead.

#047 Mar 2026

47. Vec::pop_if — Conditionally Pop the Last Element

Need to remove the last element of a Vec only when it meets a condition? Vec::pop_if does exactly that — no index juggling, no separate check-then-pop.

The old way

Before pop_if, you’d write something like this:

1
2
3
4
5
6
let mut stack = vec![1, 2, 3, 4];

if stack.last().is_some_and(|x| *x > 3) {
    let val = stack.pop().unwrap();
    println!("Popped: {val}");
}

Two separate calls, and a subtle TOCTOU gap if you’re not careful — last() checks one thing, then pop() acts on an assumption.

Enter pop_if

Stabilized in Rust 1.86, pop_if combines the check and the removal into one atomic operation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let mut stack = vec![1, 2, 3, 4];

// Pop the last element only if it's greater than 3
let popped = stack.pop_if(|x| *x > 3);
assert_eq!(popped, Some(4));
assert_eq!(stack, [1, 2, 3]);

// Now the last element is 3 — doesn't match, so nothing happens
let stayed = stack.pop_if(|x| *x > 3);
assert_eq!(stayed, None);
assert_eq!(stack, [1, 2, 3]);

The closure receives a &mut T reference to the last element. If it returns true, the element is removed and returned as Some(T). If false (or the vec is empty), you get None.

Mutable access inside the predicate

Because the closure gets &mut T, you can even modify the element before deciding whether to pop it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let mut tasks = vec![
    String::from("buy milk"),
    String::from("URGENT: deploy fix"),
];

let urgent = tasks.pop_if(|task| {
    if task.starts_with("URGENT:") {
        *task = task.replacen("URGENT: ", "", 1);
        true
    } else {
        false
    }
});

assert_eq!(urgent.as_deref(), Some("deploy fix"));
assert_eq!(tasks, vec!["buy milk"]);

A practical use: draining from the back

pop_if is handy for processing a sorted vec from the tail. Think of a priority queue backed by a sorted vec where you only want to process items above a threshold:

1
2
3
4
5
6
7
8
9
let mut scores = vec![10, 25, 50, 75, 90];

let mut high_scores = Vec::new();
while let Some(score) = scores.pop_if(|s| *s >= 70) {
    high_scores.push(score);
}

assert_eq!(high_scores, vec![90, 75]);
assert_eq!(scores, vec![10, 25, 50]);

Clean, expressive, and no off-by-one errors. Another small addition to Vec that makes everyday Rust just a bit nicer.

45. get_disjoint_mut — Multiple Mutable References at Once

The borrow checker won’t let you hold two &mut refs into the same collection — even when you know they don’t overlap. get_disjoint_mut fixes that without unsafe.

The problem

You want to update two elements of the same Vec together, but the compiler won’t allow two mutable borrows at once:

1
2
3
4
let mut scores = vec![10u32, 20, 30, 40];
let a = &mut scores[0];
let b = &mut scores[2]; // ❌ cannot borrow `scores` as mutable more than once
*a += *b;

The borrow checker doesn’t know indices 0 and 2 are different slots — it just sees two &mut to the same Vec. The classic escape hatches (split_at_mut, unsafe, RefCell) all feel like workarounds for something that should just work.

get_disjoint_mut to the rescue

Stabilized in Rust 1.86, get_disjoint_mut accepts an array of indices and returns multiple mutable references — verified at runtime to be non-overlapping:

1
2
3
4
5
6
7
let mut scores = vec![10u32, 20, 30, 40];

if let Ok([a, b]) = scores.get_disjoint_mut([0, 2]) {
    *a += *b; // 10 + 30 = 40
}

assert_eq!(scores, [40, 20, 30, 40]); // ✅

The Result is Err only if an index is out of bounds or indices overlap. Duplicate indices are caught at runtime and return Err — no silent aliasing bugs.

Works on HashMap as well

HashMap gets the same treatment. The return type is [Option<&mut V>; N] — one Option per key, since keys can be missing:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::collections::HashMap;

let mut accounts: HashMap<&str, u32> = HashMap::from([
    ("alice", 100),
    ("bob", 200),
]);

// Transfer 50 from bob to alice
let [alice, bob] = accounts.get_disjoint_mut(["alice", "bob"]);
if let (Some(a), Some(b)) = (alice, bob) {
    *a += 50;
    *b -= 50;
}

assert_eq!(accounts["alice"], 150);
assert_eq!(accounts["bob"], 150); // ✅

Passing duplicate keys to the HashMap version panics — the right tradeoff for a bug that would otherwise silently produce undefined behavior.

When to reach for it

  • Swapping or combining two elements in a Vec without split_at_mut gymnastics
  • Updating multiple HashMap entries in one pass
  • Any place you’d have used unsafe or RefCell just to hold two &mut into the same container

If your indices or keys are known not to overlap, get_disjoint_mut is the clean, safe answer.

43. Vec::extract_if — Remove Elements and Keep Them

Ever needed to split a Vec into two groups — the ones you keep and the ones you remove? retain discards the removed items. Now there’s a better way.

Vec::extract_if (stable since Rust 1.87) removes elements matching a predicate and hands them back as an iterator — in a single pass.

The old way — two passes, logic must stay in sync

1
2
3
4
5
6
7
8
9
let mut numbers = vec![1, 2, 3, 4, 5, 6];

// Collect the evens first…
let evens: Vec<i32> = numbers.iter().filter(|&&x| x % 2 == 0).copied().collect();
// …then remove them (predicate must match exactly)
numbers.retain(|&x| x % 2 != 0);

assert_eq!(numbers, [1, 3, 5]);
assert_eq!(evens,   [2, 4, 6]);

The filter and the retain predicates must be inverses of each other — easy to mistype, and you touch the data twice.

The new way — one pass, one predicate

1
2
3
4
5
6
let mut numbers = vec![1, 2, 3, 4, 5, 6];

let evens: Vec<i32> = numbers.extract_if(.., |&mut x| x % 2 == 0).collect();

assert_eq!(numbers, [1, 3, 5]);
assert_eq!(evens,   [2, 4, 6]);

extract_if walks the Vec, removes every element where the closure returns true, and yields it. The .. is a range — you can narrow it to only consider a slice of the vector.

Real-world example: draining a work queue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#[derive(Debug)]
struct Job { id: u32, priority: u8 }

let mut queue = vec![
    Job { id: 1, priority: 3 },
    Job { id: 2, priority: 9 },
    Job { id: 3, priority: 1 },
    Job { id: 4, priority: 8 },
];

// Pull out all high-priority jobs for immediate processing
let urgent: Vec<Job> = queue.extract_if(.., |j| j.priority >= 8).collect();

assert_eq!(urgent.len(), 2);  // jobs 2 and 4
assert_eq!(queue.len(),  2);  // jobs 1 and 3 remain

HashMap and HashSet also gained extract_if in Rust 1.88.

Note: The closure takes &mut T, so you can even mutate elements mid-extraction before deciding whether to remove them.

31. HashMap's entry API

Want to insert a value into a HashMap only if the key doesn’t exist yet? Skip the double lookup — use the entry API.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::collections::HashMap;

let mut scores: HashMap<&str, Vec<u32>> = HashMap::new();

// Instead of checking .contains_key() then inserting:
scores.entry("alice")
    .or_insert_with(Vec::new)
    .push(100);

scores.entry("alice")
    .or_insert_with(Vec::new)
    .push(200);

assert_eq!(scores["alice"], vec![100, 200]);

The entry API returns an Entry enum — either Occupied or Vacant. The convenience methods make common patterns a one-liner:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::collections::HashMap;

let mut word_count: HashMap<&str, usize> = HashMap::new();
let words = ["hello", "world", "hello", "rust", "hello"];

for word in words {
    *word_count.entry(word).or_insert(0) += 1;
}

// hello => 3, world => 1, rust => 1

or_insert(val) inserts a default, or_insert_with(|| val) lazily computes it, and or_default() uses the type’s Default. All three return a mutable reference to the value, so you can update in place.