Vec<T> in Depth

drain() and splice()

The .drain(range) and .splice(range, replace_with) methods remove a contiguous range of elements from a Vec<T> and return an iterator that yields the removed elements by value (T).

Signatures and Behavior

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

    pub fn splice<R, I>(&mut self, range: R, replace_with: I) -> Splice<'_, I::IntoIter>
    where
        R: RangeBounds<usize>,
        I: IntoIterator<Item = T>;
}
Method What It Removes What It Inserts in Place of range When Does the Shift Happen?
v.drain(range) Elements in range (yielded by Drain<'_, T>) Nothing When Drain is dropped (even if partially consumed!)
v.splice(range, iter) Elements in range (yielded by Splice) All elements from replace_with (can have different length than range) When Splice is dropped
let mut v = vec![10, 20, 30, 40, 50];

1. v.drain(1..4)  ──> yields 20, 30, 40; leaves v = [10, 50]
2. v.splice(1..4, [99, 100])
                  ──> yields 20, 30, 40; replaces slice with [99, 100] -> v = [10, 99, 100, 50]

Extracting a Subrange by Value with drain()

Unlike .into_iter(), which consumes the entire Vec<T> and frees its heap allocation, .drain(range) moves out only the specified range (or .. for all elements) and preserves v's allocated capacity:

fn main() {
    let mut queue = vec![
        String::from("task-1"),
        String::from("task-2"),
        String::from("task-3"),
        String::from("task-4"),
    ];

    // Move the first 2 tasks out into a new Vec; `queue` retains ["task-3", "task-4"]:
    let batch: Vec<String> = queue.drain(0..2).collect();

    println!("Drained batch:   {batch:?}");
    println!("Remaining queue: {queue:?} (cap={})", queue.capacity());
}
Drained batch:   ["task-1", "task-2"]
Remaining queue: ["task-3", "task-4"] (cap=4)

Notice that even if you do not consume the Drain iterator (let _ = queue.drain(0..2);), dropping the Drain struct automatically drops any unconsumed elements in 0..2 and shifts the remaining tail left.

Replacing a Subrange in One Operation with splice()

v.splice(range, replace_with) replaces range with any IntoIterator<Item = T>, shifting the tail of v at most once when the Splice iterator is dropped:

fn main() {
    let mut tokens = vec!["SELECT", "*", "FROM", "users"];

    // Replace "*" (index 1..2) with three column names, and collect the replaced item:
    let removed: Vec<&str> = tokens
        .splice(1..2, ["id", "email", "created_at"])
        .collect();

    println!("Removed: {removed:?}");
    println!("Updated: {tokens:?}");
}
Removed: ["*"]
Updated: ["SELECT", "id", "email", "created_at", "FROM", "users"]