A thread-local pool of reusable Vec buffers with cross-type reuse.
Allocating and deallocating Vecs in a loop is expensive. vecpool recycles the underlying heap buffers so subsequent allocations are free — just a pointer pop from a thread-local stack.
Unlike per-type pools, vecpool keys buffers by memory layout (size, align), so a buffer originally used for Vec<i32> can be reused for Vec<u32>, Vec<f32>, or any other 4-byte aligned type.
[dependencies]
vecpool = "0.1"use vecpool::{get, with_capacity, PoolVec};
// Get a vec from the pool
let mut v = get::<u32>();
v.push(1);
v.push(2);
v.push(3);
// Use it exactly like a Vec<u32>
assert_eq!(v.len(), 3);
assert_eq!(&*v, &[1, 2, 3]);
// On drop, the buffer is returned to the pool
drop(v);
// Next request reuses the buffer — zero allocation cost
let v2 = get::<u32>();
assert!(v2.capacity() >= 3);- Cross-type reuse —
Vec<i32>↔Vec<u32>↔Vec<f32>share the same pool lane - Zero synchronization — one pool per thread, no mutexes or atomics
- Transparent —
PoolVec<T>derefs toVec<T>, all Vec methods work - Fast — power-of-two sizes use a direct array lookup (
trailing_zeros), no hashing - Safe — all unsafe code is Miri-tested, no undefined behavior
| Function / Method | Description |
|---|---|
get::<T>() |
Get an empty pooled vec |
with_capacity::<T>(n) |
Get a pooled vec with at least n capacity |
PoolVec::new() |
Alias for get::<T>() |
PoolVec::from(vec) |
Wrap an existing Vec<T> for pool return on drop |
pool_vec.into_vec() |
Extract the inner Vec<T> (skips pool return) |
.collect::<PoolVec<T>>() |
Collect an iterator into a pooled vec |
pool_vec.into_iter() |
Iterate elements (buffer escapes the pool) |
clear_pool() |
Free all pooled buffers on the current thread |
- Each thread has a local pool with "lanes" keyed by
(size_of::<T>(), align_of::<T>()) - Each lane is a LIFO stack of raw buffers (pointer + byte capacity)
get::<T>()pops a buffer from the matching lane and reconstructs aVec<T>withlen = 0- On drop,
PoolVec<T>clears elements, extracts the raw buffer, and pushes it back - If no buffer is available, a fresh
Vecis allocated normally
For types where size == align and size is a power of two ≤ 256 (covers all primitives, pointers, and most small types), the pool uses a fixed-size array indexed by size.trailing_zeros(). This avoids hashing entirely.
PoolVec<T>: Send where T: Send. If a PoolVec is moved to another thread and dropped there, the buffer goes to that thread's pool. No synchronization is needed.
If the thread-local pool is being destroyed (thread exit), the buffer is freed normally instead of being pooled.
Run benchmarks with:
cargo benchThe benchmark suite compares pooled vs unpooled allocation across different element sizes, pool depths, and usage patterns.
Rust 2024 edition (1.85+).
MIT