Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
smallvec::SmallVec::into_vec for heap promotion when inline capacity is exceeded?into_vec converts a SmallVec into a standard Vec by extracting the heap-allocated storage or promoting inline storage to the heap, returning a Vec<T> that owns all elements. When the SmallVec is already using heap storage (inline capacity exceeded), this is essentially a no-cost transfer of ownership. When elements are still stored inline, into_vec allocates a heap buffer and moves elements to it. This method is used when you need to pass data to APIs expecting Vec, when the small-vector optimization is no longer beneficial, or when you want to guarantee contiguous heap storage for operations that require it.
use smallvec::SmallVec;
fn main() {
// SmallVec has inline capacity for N elements, avoiding heap allocation
// when the size is within N
type SmallIntVec = SmallVec<[i32; 4]>; // Inline storage for 4 elements
// When elements fit in inline capacity:
let mut small: SmallIntVec = SmallVec::new();
small.push(1);
small.push(2);
small.push(3);
// Elements stored inline, no heap allocation
println!("Inline: {:?}", small);
println!("On stack: {}", small.len());
// When inline capacity is exceeded:
small.push(4); // Still inline (capacity 4)
small.push(5); // NOW spills to heap!
println!("After spill: {:?}", small);
// Elements now on heap, with capacity > 4
// The inline storage is unused after spilling
// All data is on the heap
}SmallVec stores elements inline until capacity is exceeded, then moves everything to heap storage.
use smallvec::SmallVec;
fn main() {
let mut small: SmallVec<[i32; 4]> = SmallVec::new();
small.push(1);
small.push(2);
small.push(3);
// into_vec converts SmallVec to Vec
// Since elements are inline, this allocates and copies to heap
let vec: Vec<i32> = small.into_vec();
println!("Vec: {:?}", vec);
// small is now consumed, cannot be used
// With heap-allocated SmallVec:
let mut large: SmallVec<[i32; 4]> = SmallVec::new();
for i in 0..10 {
large.push(i);
}
// Already on heap (exceeded capacity 4)
let vec2: Vec<i32> = large.into_vec();
// This is essentially free - just reusing the heap allocation
println!("Vec from spilled: {:?}", vec2);
}into_vec consumes the SmallVec and returns a Vec with all elements.
use smallvec::SmallVec;
use std::time::Instant;
fn main() {
// CASE 1: Inline elements -> heap allocation required
let inline_start = Instant::now();
let small: SmallVec<[i32; 64]> = (0..32).collect(); // Fits inline
let vec1: Vec<i32> = small.into_vec(); // Allocates heap, moves elements
let inline_duration = inline_start.elapsed();
println!("Inline to Vec: {:?}", inline_duration);
// CASE 2: Already on heap -> reuse allocation
let heap_start = Instant::now();
let mut large: SmallVec<[i32; 4]> = SmallVec::new();
for i in 0..1000 {
large.push(i);
} // Already spilled to heap
let vec2: Vec<i32> = large.into_vec(); // Reuses existing heap allocation
let heap_duration = heap_start.elapsed();
println!("Heap to Vec: {:?}", heap_duration);
// The second case is faster because:
// 1. No new allocation needed (reuse existing heap buffer)
// 2. No element copying (the buffer is just transferred)
}into_vec on an already-spilled SmallVec reuses the heap buffer; inline requires allocation.
use smallvec::SmallVec;
fn main() {
type MySmallVec = SmallVec<[u64; 2]>;
// SmallVec<[T; N]> has:
// - Inline buffer: N elements on the stack
// - Pointer/capacity/len for heap when spilled
// Size of SmallVec on stack:
println!("Size of SmallVec<[u64; 2]>: {} bytes", std::mem::size_of::<MySmallVec>());
println!("Size of Vec<u64>: {} bytes", std::mem::size_of::<Vec<u64>>());
// SmallVec is larger than Vec on stack because it includes inline storage
let small: MySmallVec = SmallVec::new();
// Inline capacity: 2 u64s = 16 bytes on stack
let spilled: MySmallVec = {
let mut v = SmallVec::new();
v.push(1);
v.push(2);
v.push(3); // Spills to heap
v
};
// Both have the same stack size, but:
// - `small` has data on stack
// - `spilled` has data on heap
}SmallVec stack size includes inline buffer; spilled vectors use heap for actual data.
use smallvec::SmallVec;
fn main() {
// When SmallVec is inline:
let inline: SmallVec<[String; 4]> = SmallVec::from(vec![
"a".to_string(),
"b".to_string(),
]);
// into_vec moves each String to the new Vec
let vec1: Vec<String> = inline.into_vec();
// No String cloning - just moves
// When SmallVec is on heap:
let mut spilled: SmallVec<[String; 2]> = SmallVec::new();
spilled.push("x".to_string());
spilled.push("y".to_string());
spilled.push("z".to_string()); // Spilled
// into_vec transfers heap ownership
let vec2: Vec<String> = spilled.into_vec();
// Strings stay in place, Vec takes ownership of the heap allocation
}into_vec moves elements, not clonesâownership is transferred to the returned Vec.
use smallvec::{SmallVec, smallvec};
fn main() {
// SCENARIO 1: Interfacing with APIs expecting Vec
fn api_expecting_vec(data: Vec<i32>) -> i32 {
data.into_iter().sum()
}
let mut small: SmallVec<[i32; 8]> = smallvec![1, 2, 3, 4];
// API needs Vec, so convert
let result = api_expecting_vec(small.into_vec());
println!("Sum: {}", result);
// SCENARIO 2: Long-term storage after collection
let mut collected: SmallVec<[String; 4]> = SmallVec::new();
for word in "hello world from rust".split_whitespace() {
collected.push(word.to_string());
}
// For long-term storage, Vec may be preferred:
// - Simpler type
// - No inline buffer size needed
// - Standard drop behavior
let stored: Vec<String> = collected.into_vec();
// SCENARIO 3: Operations that require contiguous heap storage
// Some FFI or unsafe code may require heap pointers
let vec_for_ffi: Vec<u8> = smallvec![1u8, 2, 3, 4, 5].into_vec();
let ptr = vec_for_ffi.as_ptr(); // Guaranteed heap pointer
// (Vec always uses heap)
// SCENARIO 4: Growing beyond inline capacity
let mut growing: SmallVec<[i32; 4]> = SmallVec::new();
for i in 0..3 {
growing.push(i);
}
// About to add many more elements
// Convert to Vec for simpler reallocation
let mut vec: Vec<i32> = growing.into_vec();
vec.extend(0..1000); // Vec handles growth naturally
}Use into_vec when you need Vec for API compatibility or when the small-vector optimization is no longer applicable.
use smallvec::{SmallVec, smallvec};
fn main() {
let small: SmallVec<[i32; 4]> = smallvec![1, 2, 3];
// into_vec: Consumes SmallVec, returns Vec
let vec1: Vec<i32> = small.into_vec();
// small is now invalid (moved)
let small2: SmallVec<[i32; 4]> = smallvec![1, 2, 3];
// to_vec: Clones elements, returns Vec
let vec2: Vec<i32> = small2.to_vec();
// small2 is still valid
println!("After to_vec, small2: {:?}", small2);
// into_boxed_slice: Consumes, returns Box<[T]>
let small3: SmallVec<[i32; 4]> = smallvec![4, 5, 6];
let boxed: Box<[i32]> = small3.into_boxed_slice();
// Cannot resize a boxed slice
// drain: Removes elements, returns iterator
let mut small4: SmallVec<[i32; 4]> = smallvec![7, 8, 9];
let drained: Vec<i32> = small4.drain(..).collect();
// small4 is now empty but valid
println!("After drain, small4: {:?}", small4);
// Summary:
// - into_vec: Move elements, consume SmallVec
// - to_vec: Clone elements, keep SmallVec
// - into_boxed_slice: Move to boxed slice (cannot grow)
// - drain: Move elements out, leave SmallVec empty
}into_vec moves and consumes; to_vec clones and preserves; other methods have different ownership semantics.
use smallvec::SmallVec;
fn main() {
// When into_vec is called on inline data:
let small: SmallVec<[i32; 8]> = SmallVec::from_slice(&[1, 2, 3, 4]);
// Elements are inline (within capacity 8)
// into_vec does:
// 1. Allocate heap buffer with capacity >= len
// 2. Move elements from inline buffer to heap buffer
// 3. Return Vec pointing to heap buffer
// The inline buffer memory is freed with SmallVec's stack frame
// This has a cost: allocation + move
// But it's only incurred when SmallVec hasn't already spilled
// Demonstration:
let many: SmallVec<[i32; 2]> = (0..100).collect();
// Already on heap
// into_vec on spilled SmallVec:
// 1. Check: data is on heap (spilled)
// 2. Take ownership of heap buffer
// 3. Create Vec from that buffer
// No allocation or copying needed!
}into_vec allocates when inline but reuses allocation when already spilled.
use smallvec::SmallVec;
fn main() {
// Different capacities have different trade-offs
// Capacity 0: Always on heap (like Vec but with SmallVec type)
type AlwaysHeap = SmallVec<[i32; 0]>;
let always: AlwaysHeap = SmallVec::from_vec(vec![1, 2, 3]);
let vec1: Vec<i32> = always.into_vec(); // Always reuses allocation
// Capacity 1: Single element inline, otherwise heap
type OneInline = SmallVec<[i32; 1]>;
let mut one: OneInline = SmallVec::new();
one.push(42); // Inline
one.push(43); // Now on heap
let vec2: Vec<i32> = one.into_vec(); // Reuses heap
// Large capacity: Many elements inline
type ManyInline = SmallVec<[i32; 1024]>;
let many: ManyInline = (0..512).collect(); // Still inline
// into_vec will allocate and move 512 elements
let vec3: Vec<i32> = many.into_vec();
// Choosing capacity:
// - If you know max size, use that + small buffer
// - If typically small (< N) but sometimes large, use appropriate N
// - If always large, use Vec directly or SmallVec with capacity 0
}Choose inline capacity based on typical data size; into_vec behavior depends on spill state.
use smallvec::{SmallVec, smallvec};
// Common pattern: Collect into SmallVec, then convert to Vec
fn process_words(text: &str) -> Vec<String> {
// Most text has few words, so use inline storage
// But don't limit - spill to heap if needed
let mut words: SmallVec<[String; 8]> = SmallVec::new();
for word in text.split_whitespace() {
words.push(word.to_string());
}
// Convert to Vec for return
// - If <= 8 words: heap allocation
// - If > 8 words: reuse existing heap buffer
words.into_vec()
}
fn main() {
let short = process_words("hello world");
println!("Short: {:?}", short);
let long = process_words("the quick brown fox jumps over the lazy dog");
println!("Long: {:?}", long);
}
// This pattern optimizes for common case:
// - Most calls have few words -> stack allocation
// - Occasional large input -> automatic heap
// - Return Vec for compatibilityCollect in SmallVec for stack allocation, then into_vec for return value.
use smallvec::{SmallVec, smallvec};
fn main() {
// Converting to other collection types
let small: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5]; // Spilled
// Into Vec
let vec: Vec<i32> = small.into_vec();
// Note: small is consumed, need to recreate
let small2: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
// Into Box<[T]> via Vec
let boxed: Box<[i32]> = small2.into_vec().into_boxed_slice();
// Into VecDeque via Vec
use std::collections::VecDeque;
let small3: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
let deque: VecDeque<i32> = small3.into_vec().into();
// From Vec back to SmallVec
let vec = vec![1, 2, 3, 4, 5];
let small4: SmallVec<[i32; 8]> = SmallVec::from_vec(vec);
// Or use From impl
let small5: SmallVec<[i32; 8]> = vec![1, 2, 3].into();
}into_vec is a bridge between SmallVec and the broader ecosystem of Vec-based APIs.
use smallvec::SmallVec;
fn main() {
// SmallVec<[T; N]> memory layout:
// When len <= N (inline):
// Stack: [inline_buffer: N * size_of::<T>(), len: usize, capacity: usize]
// Heap: unused
// When len > N (spilled):
// Stack: [unused_inline_buffer, len: usize, capacity: usize]
// Heap: [elements...] (capacity may be > N)
type MyVec = SmallVec<[u64; 2]>;
let inline: MyVec = SmallVec::from_slice(&[1, 2]);
// Stack memory holds: [1, 2, len=2, cap=2]
// No heap allocation
let spilled: MyVec = SmallVec::from_slice(&[1, 2, 3]);
// Stack memory holds: [unused, unused, len=3, cap=heap_capacity]
// Heap holds: [1, 2, 3, ...]
// into_vec on inline:
// Vec allocates heap buffer, copies [1, 2] to it
// Stack SmallVec is dropped
// into_vec on spilled:
// Vec takes ownership of heap buffer [1, 2, 3, ...]
// No copy needed
}The spill state determines whether into_vec copies or just transfers ownership.
Behavior summary:
| SmallVec State | into_vec Behavior | Performance | |----------------|-------------------|-------------| | Inline (len <= capacity) | Allocate heap, copy elements | O(n) allocation + copy | | Spilled (len > capacity) | Transfer heap ownership | O(1), no copy |
Memory operations:
// Inline SmallVec -> Vec
// 1. Vec allocates heap buffer (size >= len)
// 2. Elements moved from SmallVec's inline buffer
// 3. SmallVec dropped (inline buffer freed with stack)
// Spilled SmallVec -> Vec
// 1. Vec takes ownership of SmallVec's heap buffer
// 2. No allocation or copying
// 3. SmallVec's stack part dropped; heap remainsWhen to use into_vec:
VecWhen to keep SmallVec:
Key insight: into_vec bridges the small-vector optimization and standard Vec. The small-vector optimization excels when data stays inlineâstack allocation avoids heap overhead entirely. But once spilled to heap, SmallVec behaves like Vec with extra metadata. into_vec formalizes this transition: when inline, it pays the cost of one heap allocation and move; when already spilled, it's essentially free, just transferring ownership. The common pattern is collecting into SmallVec during a computation (benefiting from inline storage for typical cases), then converting to Vec for storage or API compatibility. This gives you the best of both worlds: stack allocation for small data, seamless transition to heap for large data, and Vec compatibility for the broader ecosystem.