Identifying Rust's collect:<Vec<_>>() memory leak footgun
An optimization in Rust’s `collect::<Vec<_>>()` on beta reuses allocations when converting one `Vec` into another, which can silently preserve large unused capacities—especially when mapping from larger element types to smaller ones—leading to memory usage hundreds of times higher than the live data. Commenters debate whether this constitutes a true “memory leak” or a predictable consequence of growth and reuse strategies, touching on concepts like “space leaks,” the principle of least astonishment, and how other languages handle over-allocation. Several remedies and alternatives are suggested, including explicit `shrink_to_fit` calls, using `Box<[T]>` or `Box<str>` for fixed data, and rethinking data structures in memory-intensive code, while an open Rust bug report tracks how the optimization should be adjusted.
Is this really a “memory leak”?
- Many argue this is not a true leak: memory is still owned by
Vecand freed when dropped; it’s over-allocation. - Others note that in managed-language culture this sort of persistent over-allocation is often colloquially called a “memory leak” or “space leak.”
- Several posters stress that in Rust, “leak” already has a precise meaning, so using it loosely is misleading, though the practical impact (RAM exhaustion) is still serious.
What the new collect::<Vec<_>>() optimization does
- Beta Rust reuses an existing
Vecallocation when converting viainto_iter().collect(), even across type changes. - This reuse is undocumented and violates many programmers’ mental model that
collectconstructs a fresh, reasonably-sized vector. - The intent was to save allocations and copying, but it can now retain large, obsolete capacities.
Surprising capacity blow‑up (the 200x case)
- Example pattern: build large
Vec<T>with huge capacity, map/filter to fewer items of a much smaller typeU, thencollect::<Vec<U>>(). - Previously, a new appropriately-sized allocation was made; now the old large buffer is reused, so each inner
Veckeeps massive unused capacity. - In a
Vec<Vec<_>>with hundreds of thousands of inner vectors, this multiplies into gigabytes of wasted RAM. - Some see this as an outright bug; others say code should explicitly call
shrink_to_fit()when capacity matters.
Memory behavior, expectations, and standards
- Strong debate over “principle of least astonishment”: many find reuse across type/size changes, especially when
cap >> len, too surprising. - Others emphasize that the standard/library only promises a collection, not specific allocation behavior.
- Broader tangent contrasts C++’s IFNDR/UB model with Rust’s stricter, compiler-checked semantics; Rust tends to favor rejecting surprising programs over silently accepting them.
Real-world over-allocation and alternatives
- A similar issue appeared in an Aho–Corasick implementation: many small
Vecs with growth-by-doubling strategy doubled peak memory vs. a C implementation. - Switching to linked-list–style structures (with indices) cut memory substantially, showing
Vecisn’t always the right choice.
Mitigations and alternative types
- Suggested mitigations: use
shrink_to_fit,Box<[T]>for immutable arrays,Box<str>/Arc<str>for strings, or design data structures that avoid many growableVecs. - Some propose heuristics in
collect(e.g., shrink when capacity exceeds a multiple of length), but others worry about hidden overhead and complexity.