How does smallvec::SmallVec::from_vec differ from from_slice for initial capacity optimization?
SmallVec::from_vec can take ownership of an existing heap allocation when the Vec is larger than the inline capacity, avoiding a new allocation and element copies, while from_slice must always copy elements from the borrowed slice into either inline storage or a new heap allocation. The key difference is that from_vec receives an owned Vec which may already have the correct heap capacity, whereas from_slice receives only a borrow and must allocate new storage for the elements.
SmallVec Basics
use smallvec::SmallVec;
// SmallVec stores elements inline until capacity exceeded
// Then spills to heap
// SmallVec<[u8; 8]> stores up to 8 bytes inline
type InlineVec = SmallVec<[u8; 8]>;
fn basic_usage() {
// Small collection: stored inline (no heap allocation)
let mut small: InlineVec = SmallVec::new();
small.push(1);
small.push(2);
small.push(3);
// Only 3 bytes used, fits in inline storage
// Large collection: spills to heap
let mut large: InlineVec = SmallVec::new();
for i in 0..100 {
large.push(i as u8);
}
// 100 bytes exceeds inline capacity, heap allocated
println!("Small len: {}, on stack", small.len());
println!("Large len: {}, on heap", large.len());
}SmallVec stores small collections on the stack, spilling to heap only when needed.
The Inline Capacity Trade-off
use smallvec::SmallVec;
fn inline_capacity() {
// SmallVec<[T; N]> has inline capacity for N elements
// Inline storage size = N * sizeof(T)
// SmallVec<[u64; 4]> = 32 bytes inline storage
type U64Vec = SmallVec<[u64; 4]>;
// Up to 4 u64s: no heap allocation
let mut inline: U64Vec = SmallVec::new();
inline.push(1);
inline.push(2);
inline.push(3);
inline.push(4);
// Still inline - exactly at capacity
// 5th element: spills to heap
inline.push(5);
// Now heap allocated with capacity >= 5
// The spill operation:
// 1. Allocates heap memory
// 2. Moves inline elements to heap
// 3. Adds new element
}Inline capacity determines when spill to heap occurs.
from_slice: Copying from Borrowed Data
use smallvec::SmallVec;
fn from_slice_basic() {
// from_slice takes &[T] and copies elements
let slice: &[u8] = &[1, 2, 3, 4, 5];
// Create SmallVec from slice
let vec: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// What happens:
// 1. Check if slice.len() <= inline capacity (8)
// 2. Yes - copy elements into inline storage
// 3. No - allocate heap and copy
assert_eq!(&*vec, slice);
// Small slice: inline
let small_slice: &[u8] = &[1, 2, 3];
let small_vec: SmallVec<[u8; 8]> = SmallVec::from_slice(small_slice);
// Copied to inline storage, no allocation
// Large slice: heap
let large_slice: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let large_vec: SmallVec<[u8; 8]> = SmallVec::from_slice(large_slice);
// 10 elements > 8 capacity
// Allocated heap memory, copied all elements
}from_slice always copies elements from the borrowed slice.
from_vec: Taking Ownership
use smallvec::SmallVec;
fn from_vec_basic() {
// from_vec takes Vec<T> and can reuse allocation
let vec: Vec<u8> = vec![1, 2, 3, 4, 5];
// Create SmallVec from Vec
let small_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// What happens:
// 1. Check if vec.len() <= inline capacity (8)
// 2. Yes - move elements into inline storage
// 3. No - take ownership of Vec's heap allocation
// Small Vec: moves to inline
let small: Vec<u8> = vec![1, 2, 3];
let into_inline: SmallVec<[u8; 8]> = SmallVec::from_vec(small);
// Elements moved from Vec's heap to SmallVec inline storage
// Vec's heap allocation is freed
// Large Vec: takes ownership
let large: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let takes_heap: SmallVec<[u8; 8]> = SmallVec::from_vec(large);
// 10 elements > 8 capacity
// SmallVec takes ownership of Vec's existing heap allocation
// No new allocation, no element copying!
}from_vec can reuse the Vec's heap allocation for large collections.
Key Difference: Allocation Behavior
use smallvec::SmallVec;
fn allocation_comparison() {
// Scenario 1: Small collection fits inline
// from_slice: copy to inline
let slice_small: &[u8] = &[1, 2, 3];
let from_slice: SmallVec<[u8; 8]> = SmallVec::from_slice(slice_small);
// Cost: 3 element copies to inline storage
// from_vec: move to inline
let vec_small: Vec<u8> = vec![1, 2, 3];
let from_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec_small);
// Cost: 3 element moves to inline + free Vec's heap allocation
// Scenario 2: Large collection exceeds inline
// from_slice: allocate + copy
let slice_large: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let from_slice: SmallVec<[u8; 8]> = SmallVec::from_slice(slice_large);
// Cost: 1 allocation + 10 element copies
// from_vec: take ownership
let vec_large: Vec<u8> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let from_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec_large);
// Cost: 0 allocations + 0 copies (takes existing allocation)
}from_vec with large collections avoids allocation entirely.
Capacity Preservation
use smallvec::SmallVec;
fn capacity_behavior() {
// from_slice: capacity = length (minimal)
let slice: &[u8] = &[1, 2, 3];
let from_slice: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// Inline: capacity is exactly the inline capacity (8)
// Heap (if spilled): capacity equals length exactly
// from_vec: preserves Vec's capacity
let mut vec: Vec<u8> = Vec::with_capacity(100);
vec.push(1);
vec.push(2);
vec.push(3);
// vec has len=3, capacity=100
let from_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// SmallVec takes the Vec's allocation
// len=3, capacity=100 (preserved from Vec)
// This means from_vec can preserve over-allocated capacity
// from_slice always creates minimal capacity
}
fn capacity_for_large() {
// For collections that spill to heap:
// from_slice: new allocation with capacity == length
let slice: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let small_vec: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// capacity = 10 (exactly length)
// from_vec: uses Vec's capacity
let vec: Vec<u8> = Vec::with_capacity(1000);
let vec_clone: Vec<u8> = vec.clone();
let small_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// capacity = 1000 (preserved from Vec)
}from_vec preserves the source Vec's capacity; from_slice creates minimal capacity.
Memory Layout
use smallvec::SmallVec;
fn memory_layout() {
// SmallVec<[T; N]> has two storage modes:
// Mode 1: Inline
// Elements stored directly in SmallVec struct
// Size = sizeof(data) + sizeof(len) + sizeof(capacity)
// Where data has room for N elements
// Mode 2: Heap
// Pointer to heap allocation
// Size = sizeof(ptr) + sizeof(len) + sizeof(capacity)
// from_vec with small Vec:
let small: Vec<u8> = vec![1, 2, 3];
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(small);
// Vec's heap freed, elements moved to inline
// from_vec with large Vec:
let large: Vec<u8> = (0..100).collect();
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(large);
// Vec's heap pointer transferred to SmallVec
// No deallocation, no reallocation
// from_slice always copies:
let slice: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sv: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// Copy from slice to new allocation (inline or heap)
}from_vec with large collections is a pointer transfer, not a copy.
Performance Comparison
use smallvec::SmallVec;
fn performance_small() {
// Small collection (fits inline)
let data: &[u8] = &[1, 2, 3, 4];
let vec: Vec<u8> = vec![1, 2, 3, 4];
// from_slice: copy 4 bytes to inline
let from_slice: SmallVec<[u8; 8]> = SmallVec::from_slice(data);
// Cost: 4 byte copies
// from_vec: move 4 bytes to inline, free Vec allocation
let from_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// Cost: 4 byte moves + 1 heap free
// For small collections: from_slice may be slightly faster
// (no heap free operation)
}
fn performance_large() {
// Large collection (exceeds inline)
let data: &[u8] = &[0u8; 1000];
let vec: Vec<u8> = vec![0u8; 1000];
// from_slice: allocate + copy 1000 bytes
let from_slice: SmallVec<[u8; 8]> = SmallVec::from_slice(data);
// Cost: 1 allocation + 1000 byte copies
// from_vec: take ownership (pointer copy)
let from_vec: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// Cost: ~0 (just pointer transfer)
// For large collections: from_vec is much faster
// O(1) vs O(n) and avoids allocation
}from_vec is O(1) for large collections; from_slice is always O(n).
When to Use Each
use smallvec::SmallVec;
fn choosing_constructor() {
// Use from_slice when:
// - You only have a slice (borrowed data)
// - Collection is small (fits inline)
// - You don't need preserved capacity
let slice: &[u8] = &[1, 2, 3];
let sv: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// Use from_vec when:
// - You own a Vec and won't need it anymore
// - Collection may be large (avoids allocation)
// - Vec has excess capacity you want to preserve
// - You want to avoid copying large data
let vec: Vec<u8> = (0..1000).collect();
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// Zero-copy transfer for large data
// Conversion from existing API:
fn takes_slice(data: &[u8]) -> SmallVec<[u8; 16]> {
SmallVec::from_slice(data) // Must use from_slice
}
fn takes_vec(data: Vec<u8>) -> SmallVec<[u8; 16]> {
SmallVec::from_vec(data) // Use from_vec for efficiency
}
}Choose based on ownership and size characteristics.
From Implementation Details
use smallvec::SmallVec;
// Simplified implementation logic:
// from_slice always copies:
// fn from_slice(slice: &[T]) -> Self {
// let mut vec = Self::new();
// vec.extend_from_slice(slice);
// vec
// }
// from_vec checks size:
// fn from_vec(vec: Vec<T>) -> Self {
// if vec.len() <= N {
// // Move elements to inline storage
// // Free Vec's heap allocation
// } else {
// // Take ownership of Vec's heap allocation
// // No copy needed
// }
// }
fn implementation_consequences() {
// from_slice:
// - Always creates new storage (inline or heap)
// - Always copies all elements
// - Capacity is exactly length (or inline capacity)
// - Input slice is still valid after
// from_vec:
// - May create inline storage (small vec)
// - May reuse heap allocation (large vec)
// - Capacity preserved from Vec
// - Input Vec is consumed (moved)
let vec: Vec<u8> = vec![1, 2, 3];
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// vec is moved, cannot use after
let slice: &[u8] = &[1, 2, 3];
let sv: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// slice is still valid and usable
}from_vec consumes ownership; from_slice borrows.
Practical Examples
use smallvec::SmallVec;
// Example 1: Parsing small results
fn parse_small(input: &str) -> SmallVec<[char; 32]> {
// Use from_slice - we have borrowed data
SmallVec::from_slice(input.chars().as_str().as_bytes())
// Wait, chars() doesn't give us a slice directly
// Actually we'd collect here
input.chars().collect()
}
// Example 2: Converting Vec to SmallVec
fn convert_large(vec: Vec<u8>) -> SmallVec<[u8; 16]> {
// Use from_vec - we own the Vec
SmallVec::from_vec(vec)
// Zero allocation if vec.len() > 16
}
// Example 3: Function returning SmallVec
fn process_data(input: &[u8]) -> SmallVec<[u8; 64]> {
if input.len() <= 64 {
// Small: from_slice copies to inline
SmallVec::from_slice(input)
} else {
// Large: collect into Vec first, then convert
let vec: Vec<u8> = input.to_vec();
SmallVec::from_vec(vec)
// Wait, this still copies in from_vec if <= 64
// Actually for >64, it takes the heap allocation
}
}
// Example 4: Preserving capacity
fn with_capacity_example() -> SmallVec<[u8; 8]> {
let mut vec = Vec::with_capacity(1000);
vec.extend(0..10); // Only 10 elements
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// sv has capacity 1000, len 10
// Can push 990 more elements without reallocation
// If we used from_slice:
let slice: &[u8] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let sv2: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// sv2 has capacity 10 (minimal)
sv
}Real-world usage depends on ownership and capacity needs.
Spill Behavior Differences
use smallvec::SmallVec;
fn spill_behavior() {
// When collection exceeds inline capacity:
// from_slice with large slice:
let large_slice: &[u8] = &[0u8; 100];
let mut sv1: SmallVec<[u8; 8]> = SmallVec::from_slice(large_slice);
// Immediately allocates heap with capacity 100
// All elements copied to new heap
// from_vec with large Vec:
let large_vec: Vec<u8> = vec![0u8; 100];
let mut sv2: SmallVec<[u8; 8]> = SmallVec::from_vec(large_vec);
// Takes Vec's existing heap allocation
// No new allocation, no copy
// After creation, both behave the same:
sv1.push(101); // May reallocate if capacity exceeded
sv2.push(101); // May reallocate if capacity exceeded
// But sv2 had preserved capacity from Vec
// If Vec had spare capacity, sv2 has it too
}
fn spill_from_small() {
// from_slice with small slice (fits inline):
let small_slice: &[u8] = &[1, 2, 3];
let mut sv1: SmallVec<[u8; 8]> = SmallVec::from_slice(small_slice);
// Stored inline
// from_vec with small Vec (fits inline):
let small_vec: Vec<u8> = vec![1, 2, 3];
let mut sv2: SmallVec<[u8; 8]> = SmallVec::from_vec(small_vec);
// Also stored inline (Vec's heap freed)
// Both spilled similarly when growing:
sv1.extend(0..10); // Spills to heap
sv2.extend(0..10); // Spills to heap
}Both constructors spill similarly when growing; initial capacity differs.
Benchmark Comparison
use smallvec::SmallVec;
// Conceptual performance comparison:
// from_slice with small data (fits inline):
// Cost: O(n) element copies to inline storage
// Memory: sizeof(SmallVec) on stack
// Allocation: 0
// from_slice with large data (exceeds inline):
// Cost: O(n) element copies + 1 allocation
// Memory: heap allocation
// Allocation: 1
// from_vec with small data (fits inline):
// Cost: O(n) element moves to inline + 1 deallocation
// Memory: sizeof(SmallVec) on stack
// Allocation: -1 (frees Vec's heap)
// from_vec with large data (exceeds inline):
// Cost: O(1) pointer transfer
// Memory: Vec's existing heap (transferred)
// Allocation: 0
// Rule of thumb:
// - Large Vec -> SmallVec: from_vec is O(1)
// - Large slice -> SmallVec: from_slice is O(n) + allocation
// - Small data: both are O(n) copies/moves
fn rule_of_thumb() {
// You have a Vec:
let vec: Vec<u8> = get_vec();
let sv: SmallVec<[u8; 8]> = SmallVec::from_vec(vec);
// Always use from_vec (handles both cases efficiently)
// You have a slice:
let slice: &[u8] = get_slice();
let sv: SmallVec<[u8; 8]> = SmallVec::from_slice(slice);
// Only option for borrowed data
// You're building from scratch:
let mut sv: SmallVec<[u8; 8]> = SmallVec::new();
// Or SmallVec::with_capacity(n) for known size
}Choose based on data source: owned Vec vs borrowed slice.
Synthesis
Key differences:
| Aspect | from_slice |
from_vec |
|---|---|---|
| Input | &[T] (borrow) |
Vec<T> (owned) |
| Copying | Always copies | May take ownership |
| Allocation | New for large | Reuses Vec's for large |
| Capacity | Minimal (== len) | Preserved from Vec |
| Small data | Copy to inline | Move to inline + free Vec |
| Large data | Allocate + copy | Pointer transfer |
Cost comparison:
// Small collection (len <= N):
// from_slice: O(n) copies to inline
// from_vec: O(n) moves to inline + free Vec heap
// Large collection (len > N):
// from_slice: O(n) copies + 1 allocation
// from_vec: O(1) pointer transfer (no copy, no allocation)Decision guide:
// Use from_slice when:
// - You have borrowed data (slice)
// - Data is small and fits inline
// - You don't need preserved capacity
// Use from_vec when:
// - You have an owned Vec
// - Data may be large (avoids allocation)
// - Vec has useful spare capacity
// - You want zero-copy for large data
// If you have a choice (can convert Vec to slice or vice versa):
// - Large data: prefer from_vec (zero-copy)
// - Small data: from_slice slightly faster (no deallocation)Key insight: SmallVec::from_vec can take ownership of an existing heap allocation when the source Vec exceeds inline capacity, making it O(1) for large collections compared to from_slice which must always allocate and copy. For small collections that fit inline, both methods are O(n), but from_vec has the added cost of freeing the Vec's heap allocation while from_slice simply copies to inline storage. The choice between them depends on whether you have ownership of the source data and whether preserving the Vec's capacity matters for your use case.
