July 26, 20253 min

Vec::drain() Vs Vec::truncate() or Vec::clear()?

m
mayo

What is Vec::drain()?

drain() removes a range of elements from a Vec while yielding ownership of them through an iterator. Unlike truncate() or clear(), it allows you to process the removed elements before they're dropped.

Signature

pub fn drain<R>(&mut self, range: R) -> Drain<'_, T>
where
    R: RangeBounds<usize>,

Key Features

Method Removes Elements Yields Ownership Preserves Capacity Time Complexity
drain(..) Yes ✅ Yes (via iterator) ✅ Yes O(n)
truncate() Yes (from index) ❌ No ✅ Yes O(1)
clear() All ❌ No ✅ Yes O(1)

When to Use Each

1. Vec::drain()

Use Case: Process removed elements (e.g., filter, transform, or batch-delete).

Example:

let mut vec = vec!['a', 'b', 'c', 'd'];
for ch in vec.drain(1..3) {  // Removes 'b' and 'c'
    println!("Removed: {}", ch);  // Prints 'b', then 'c'
}
assert_eq!(vec, ['a', 'd']);  // Keeps remaining elements

Performance: Avoids extra allocations if reusing the iterator.

2. Vec::truncate()

Use Case: Quickly remove elements from the end without processing them.

Example:

let mut vec = vec![1, 2, 3, 4];
vec.truncate(2);  // Drops 3 and 4 (no iterator)
assert_eq!(vec, [1, 2]);

3. Vec::clear()

Use Case: Remove all elements (faster than drain(..) if you don't need them).

Example:

let mut vec = vec![1, 2, 3];
vec.clear();  // Drops all elements
assert!(vec.is_empty());

Memory Behavior

  • All three methods retain the Vec's capacity (no reallocation if elements are re-added).
  • drain() is lazy: Elements are only dropped when the iterator is consumed.

Advanced Use: Reuse Storage

drain() is ideal for replacing a subset of elements efficiently:

let mut vec = vec!["old", "old", "new", "old"];
vec.drain(0..2).for_each(drop);  // Remove first two
vec.insert(0, "fresh");
assert_eq!(vec, ["fresh", "new", "old"]);

Key Takeaways

  • drain(): Use when you need to process removed elements or batch-delete.
  • truncate()/clear(): Use for fast bulk removal without processing.
  • 🚀 All preserve capacity: No reallocation overhead for future ops.

Real-World Example

In a game engine, drain() could efficiently remove expired entities while allowing cleanup logic (e.g., saving state).

Try This: What happens if you drain() but don't consume the iterator?

Answer: The elements are still removed when the Drain iterator is dropped (due to its Drop impl).