July 26, 20256 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.

vec!['a','b','c','d'] — three ways to remove elements ['a','b','c','d'] drain(1..3) truncate(2) clear() ['a','d'] ['a','b'] [] yields 'b','c' via iterator drops tail, no iterator drops all, no iterator All three preserve the Vec's capacity — only drain() lets you use the removed values

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.

That laziness means the interesting work is spread over the lifetime of the Drain guard, not concentrated in the drain() call:

Lifetime of the Drain guard from vec.drain(1..3) 1 · drain(1..3) called len set to 1 up front, &mut vec held by the guard 2 · next() yields 'b', 'c' ownership moves to you, one element at a time 3 · guard dropped anything not yielded is dropped right here 4 · tail restored 'd' shifted left, vec is ['a','d'], capacity kept Steps 3 and 4 run from Drain's Drop impl, so the removal happens even if you never call next(). While the guard lives, vec is mutably borrowed and cannot be touched.

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).