Table of Contents
Flattening with Iterators
The most idiomatic way is to use .flatten()
or .flat_map()
:
let nested = vec![vec![1, 2], vec![3], vec![4, 5, 6]];
// Method 1: flatten() (for Vec<Iterables>)
let flat: Vec<_> = nested.iter().flatten().copied().collect();
// Method 2: flat_map() (for custom transformations)
let flat: Vec<_> = nested.into_iter().flat_map(|v| v).collect();
Output: [1, 2, 3, 4, 5, 6]
Manual Concatenation
For comparison, here's how you might do it manually:
let mut flat = Vec::new();
for subvec in nested {
flat.extend(subvec); // or append() if subvec is no longer needed
}
Performance Comparison
Method | Time Complexity | Space Complexity | Allocations | Optimizations |
---|---|---|---|---|
Iterator (flatten) | O(n) | O(1) iterator | 1 (result) | May fuse iterators |
Manual (extend) | O(n) | O(1) temp space | 1 (result) | Pre-allocation possible |
Key Insights
Pre-allocation Advantage (Manual)
You can pre-allocate the target Vec if total size is known:
let total_len: usize = nested.iter().map(|v| v.len()).sum();
let mut flat = Vec::with_capacity(total_len); // Critical for large datasets
flat.extend(nested.into_iter().flatten());
Iterator Laziness
.flatten()
is lazy, but.collect()
still needs to allocate the result.- Chained iterators (e.g.,
.filter().flatten()
) may optimize better than manual loops.
Benchmark Example
let nested: Vec<Vec<i32>> = (0..1_000).map(|i| vec![i; 100]).collect();
// Iterator approach
let start = std::time::Instant::now();
let flat = nested.iter().flatten().copied().collect::<Vec<_>>();
println!("flatten: {:?}", start.elapsed());
// Manual approach with pre-allocation
let start = std::time::Instant::now();
let total_len = nested.iter().map(|v| v.len()).sum();
let mut flat = Vec::with_capacity(total_len);
flat.extend(nested.into_iter().flatten());
println!("manual: {:?}", start.elapsed());
Typical Result:
- Manual with pre-allocation is ~10–20% faster for large Vecs.
- Iterator version is more concise and equally fast for small data.
When to Use Each
Approach | Best For | Pitfalls |
---|---|---|
Iterator | Readability, chaining operations | Slightly slower without pre-allocation |
Manual | Maximum performance, large data | Verbose; requires length calculation |
Advanced: Zero-Copy Flattening
If you have Vec<&[T]>
instead of Vec<Vec<T>>
, use .flatten().copied()
to avoid cloning:
let slices: Vec<&[i32]> = vec![&[1, 2], &[3, 4]];
let flat: Vec<i32> = slices.iter().flatten().copied().collect();
Key Takeaways
✅ Use .flatten() for:
- Clean, idiomatic code.
- Chaining with other iterator adapters (e.g.,
.filter()
).
✅ Use manual extend for:
- Large datasets where pre-allocation matters.
- Cases where you already know the total length.
🚀 Always pre-allocate for manual concatenation of large collections!
Try This: How would you flatten a Vec<Vec<T>>
while removing duplicates?
Answer: Combine .flatten()
with .collect::<HashSet<_>>()
.
Back to Blog
Share: