Vec<T> is a dynamic, growable array that stores elements in a single allocated (on the heap) block of memory. Adding a new element (push) increases the length, and if necessary, new memory is allocated (reallocated). When pushing, the capacity increases exponentially to avoid constant reallocations. When removing an element (pop/remove), the capacity does not automatically decrease.
A common issue is excessive allocations and reallocations during constant additions.
Example of using pre-allocation:
let mut v = Vec::with_capacity(1000); for i in 0..1000 { v.push(i); } assert_eq!(v.capacity(), 1000);
Question: What will happen to the capacity of the vec after calling v.shrink_to_fit()? Will it be exactly equal to the length?
Incorrect answer: Yes, always, after shrink_to_fit capacity == len.
Correct answer: Not necessarily, the implementation of shrink_to_fit is a "suggestion" to the allocator. It usually aims for the minimally possible capacity, but specifics may vary depending on the allocator's implementation (for example, it may remain above the length).
Example:
let mut v = Vec::with_capacity(10); for i in 0..5 { v.push(i); } v.shrink_to_fit(); // capacity ≥ len (5), but it's not guaranteed that == len
Story
A developer repeatedly pushed objects into Vec without setting capacity, leading to exponential growth of reallocations with large data volumes, slowing down the processing in "heavy" loops. Optimizing with
with_capacityreduced the time by 10 times.
Story
The team tried to save memory by regularly calling
shrink_to_fitafter each pop(). As a result, cycles of constant realloc/free occurred, degrading performance and nearly pushing the service to a DoS.
Story
A Vec was left as a field of a structure with internal references to its elements. After reallocation (push beyond capacity), the references became invalid — the resulting bugs were difficult to trace until production execution.