Vec With Capacity and Reusing Buffers in Rust Loops
Vec::with_capacity avoids repeated regrowth, and clear keeps capacity for reuse. See how growth works, when pre-sizing helps and how to avoid holding oversized buffers forever.

Pushing a million items onto an empty Vec reallocates around twenty times, copying the contents each time. That is fast enough that nobody notices until the same code runs in a hot path. The fix costs one argument. This post shows how capacity works, how to observe it, and the opposite trap: keeping a buffer so large that it wastes memory. It is part of the allocation reduction series started in reducing allocations.
Rust 1.90, edition 2024.
Watch it grow
fn main() {
let mut v: Vec<u32> = Vec::new();
let mut last = v.capacity();
println!("start cap {last}");
for i in 0..40 {
v.push(i);
if v.capacity() != last {
last = v.capacity();
println!("len {} -> cap {}", v.len(), last);
}
}
}The growth steps depend on the implementation. On current standard libraries the first push allocates room for 4 elements and later growth doubles, giving capacities such as 4, 8, 16, 32 and 64. The exact numbers are an implementation detail and not a guarantee, so program against capacity() >= n and never against specific values.
Pre-size
let mut ids = Vec::with_capacity(rows.len());
for row in &rows {
ids.push(row.id);
}One allocation of the right size. If the size is only an estimate, an over-estimate wastes memory and an under-estimate just regrows once. reserve(additional) adds room to an existing vector, and reserve_exact asks for exactly that much without the usual extra.
Reuse in a loop
let mut line = String::with_capacity(256);
let mut total = 0;
for record in records {
line.clear();
line.push_str(&record.name);
line.push(',');
total += line.len();
}clear() runs destructors on elements (none for u8/char data) and keeps the capacity. The String here is allocated once in a typical run. The same holds for Vec<T>, HashMap::clear and VecDeque::clear.
When the buffer stays huge
A buffer reused for years keeps the memory of its biggest use. In a server that handles one 200 MB upload per day, a pooled Vec holds 200 MB all day. Options:
- Bound reuse: if
buf.capacity() > LIMIT, replace with a freshVecafter use. shrink_to(limit)reduces capacity to at least the limit, giving memory back.- Do not pool at all if allocation is not on a hot path.
collect and size hints
(0..n).map(f).collect::<Vec<_>>() allocates once because the range reports an exact size. .filter(...) reports a minimum of zero, so the Vec grows as it goes; add Vec::with_capacity and extend if the result size is predictable. See iterators versus loops.
Limits
Capacity has no effect on correctness, only on cost, and the cost is small until it is repeated millions of times. Do not carry a manual capacity field alongside a Vec, because the vector tracks it for you. The behavior is documented in the standard library under Vec capacity and reallocation.


