Loading pageā¦
Rust walkthroughs
Loading pageā¦
smallvec::SmallVec::from_vec and from_slice for collection initialization?SmallVec::from_vec and SmallVec::from_slice differ fundamentally in ownership semantics and allocation behavior: from_vec takes ownership of an existing Vec, potentially reusing its heap allocation without copying elements, while from_slice borrows a slice and copies all elements into new storage. from_vec is more efficient when you already have a Vec because it can either move elements inline (if they fit within the small-array capacity) or take ownership of the entire heap allocation (if the Vec is larger than inline capacity). from_slice requires the element type to implement Clone because it must copy every element, and it always allocates new storageāeither inline or on the heap depending on the slice length. The choice between them depends on whether you own the source data and whether you can afford to move it, with from_vec being zero-copy when possible and from_slice always involving element duplication.
use smallvec::{SmallVec, smallvec};
fn main() {
// from_vec: takes ownership of Vec
let vec = vec![1, 2, 3, 4, 5];
let smallvec: SmallVec<[i32; 4]> = SmallVec::from_vec(vec);
// 'vec' is moved, no longer accessible
// from_slice: copies from a borrowed slice
let slice = &[1, 2, 3, 4, 5];
let smallvec: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// 'slice' is still valid, elements were copied
println!("from_vec smallvec: {:?}", smallvec);
println!("slice still valid: {:?}", slice);
}from_vec consumes the source; from_slice borrows and copies.
use smallvec::SmallVec;
// Type that is expensive to clone
#[derive(Debug)]
struct ExpensiveToClone {
id: u32,
data: Vec<u8>, // Potentially large data
}
// from_vec doesn't require Clone
fn from_vec_example() -> SmallVec<[ExpensiveToClone; 2]> {
let vec = vec![
ExpensiveToClone { id: 1, data: vec![1, 2, 3] },
ExpensiveToClone { id: 2, data: vec![4, 5, 6] },
];
// This works even though ExpensiveToClone doesn't implement Clone!
SmallVec::from_vec(vec)
}
// from_slice DOES require Clone
fn from_slice_example() -> SmallVec<[ExpensiveToClone; 2]> {
let items = [
ExpensiveToClone { id: 1, data: vec![1, 2, 3] },
ExpensiveToClone { id: 2, data: vec![4, 5, 6] },
];
// This would fail to compile:
// SmallVec::from_slice(&items)
// error: the trait `Clone` is not implemented for `ExpensiveToClone`
// If ExpensiveToClone implemented Clone, this would copy both elements
todo!()
}
fn main() {
let sv = from_vec_example();
println!("{:?}", sv);
}from_vec works without Clone; from_slice requires it.
use smallvec::SmallVec;
fn main() {
// SmallVec with inline capacity of 4
// Elements <= 4 are stored inline, > 4 go to heap
// Case 1: Vec fits inline
let small = vec![1, 2, 3];
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(small);
// Elements moved from Vec's heap to inline storage
// Vec's heap allocation freed
println!("Fits inline: {:?}", sv);
// Case 2: Vec exceeds inline capacity
let large = vec![1, 2, 3, 4, 5, 6, 7, 8];
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(large);
// SmallVec takes ownership of Vec's heap allocation
// No copying or reallocation!
println!("Exceeds inline: {:?}", sv);
// Case 3: from_slice with small data
let slice = &[1, 2, 3];
let sv: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// Elements copied into inline storage
println!("from_slice small: {:?}", sv);
// Case 4: from_slice with large data
let slice = &[1, 2, 3, 4, 5, 6, 7, 8];
let sv: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// Elements copied into new heap allocation
// (if large enough; otherwise inline)
println!("from_slice large: {:?}", sv);
}from_vec reuses heap allocation; from_slice always copies.
use smallvec::SmallVec;
fn allocation_behavior_inline() {
// Inline capacity = 4
// from_vec with small Vec (capacity <= 4)
let vec: Vec<i32> = Vec::with_capacity(4);
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(vec);
// Vec's allocation freed
// SmallVec uses inline storage
// Result: 1 deallocation (Vec's), inline storage used
// from_slice with small slice
let slice = &[1, 2, 3, 4];
let sv: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// No heap allocation needed
// Elements copied to inline storage
// Result: 0 allocations, N copies
}
fn allocation_behavior_heap() {
// from_vec with large Vec
let mut vec: Vec<i32> = Vec::with_capacity(1000);
vec.extend(0..1000);
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(vec);
// Vec's heap allocation is reused
// SmallVec points to existing allocation
// Result: 0 allocations, 0 copies!
// from_slice with large slice
let slice: &[i32] = &(0..1000).collect::<Vec<_>>()[..];
let sv: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// New heap allocation required
// All elements copied
// Result: 1 allocation, N copies
}from_vec can be zero-copy; from_slice always copies.
use smallvec::SmallVec;
use std::time::Instant;
fn main() {
const COUNT: usize = 100_000;
// Benchmark: from_vec vs from_slice for large collections
// from_vec: O(1) for heap case, O(n) for inline case
let start = Instant::now();
for _ in 0..COUNT {
let vec: Vec<u64> = (0..100).collect();
let sv: SmallVec<[u64; 4]> = SmallVec::from_vec(vec);
std::hint::black_box(sv);
}
let from_vec_time = start.elapsed();
println!("from_vec: {:?}", from_vec_time);
// from_slice: O(n) always (must copy elements)
let data: Vec<u64> = (0..100).collect();
let start = Instant::now();
for _ in 0..COUNT {
let sv: SmallVec<[u64; 4]> = SmallVec::from_slice(&data);
std::hint::black_box(sv);
}
let from_slice_time = start.elapsed();
println!("from_slice: {:?}", from_slice_time);
// from_vec is typically faster because:
// 1. No element copying when Vec exceeds inline capacity
// 2. Even for inline case, may be faster due to ownership semantics
}from_vec avoids copying when possible; from_slice always copies.
use smallvec::SmallVec;
fn main() {
// When data fits inline, both methods copy, but differently:
// SmallVec<[i32; 8]> - capacity for 8 elements inline
let data = vec![1, 2, 3, 4, 5];
// from_vec: moves elements from Vec's heap to inline
let sv1: SmallVec<[i32; 8]> = SmallVec::from_vec(data);
// Vec's heap allocation freed
// Elements moved individually into inline storage
let slice = &[1, 2, 3, 4, 5];
// from_slice: copies from slice to inline
let sv2: SmallVec<[i32; 8]> = SmallVec::from_slice(slice);
// Elements copied from slice to inline storage
// Both result in inline storage, but:
// - from_vec frees Vec's allocation
// - from_slice keeps source slice intact
// For types implementing Copy, the difference is minimal
// For complex types, from_vec may avoid expensive clones
}Both methods populate inline storage, but ownership differs.
use smallvec::SmallVec;
fn main() {
// When Vec exceeds inline capacity, from_vec reuses allocation
// Vec with capacity 1000 (exceeds inline capacity of 4)
let vec: Vec<String> = (0..1000)
.map(|i| format!("string_{}", i))
.collect();
// Vec has allocated memory on heap
let vec_capacity = vec.capacity();
println!("Vec capacity: {}", vec_capacity);
// from_vec takes ownership of this allocation
let sv: SmallVec<[String; 4]> = SmallVec::from_vec(vec);
// No strings are copied or cloned!
// The SmallVec uses the same heap allocation
// This is a pointer transfer, not an element copy
// Compare with from_slice:
let vec2: Vec<String> = (0..1000)
.map(|i| format!("string_{}", i))
.collect();
let slice: &[String] = &vec2;
// from_slice would clone all 1000 strings
// let sv2: SmallVec<[String; 4]> = SmallVec::from_slice(slice);
// This would be expensive!
println!("SmallVec uses heap: {}", !sv.is_inline());
}from_vec reuses heap allocation; from_slice would clone all elements.
use smallvec::SmallVec;
// SmallVec is roughly:
//
// struct SmallVec<[T; N]> {
// // Inline storage for N elements
// inline: [MaybeUninit<T>; N],
// // Or pointer to heap allocation
// heap_ptr: NonNull<T>,
// // Current length and capacity info
// len: usize,
// // Flag indicating whether using inline or heap
// }
//
// from_vec(Vec<T>):
// - If Vec capacity <= N: move elements into inline, free Vec's allocation
// - If Vec capacity > N: take ownership of Vec's RawVec (heap allocation)
// - No Clone needed because ownership is transferred
//
// from_slice(&[T]) where T: Clone:
// - Always allocates (inline or heap) and copies all elements
// - Requires Clone because elements are copied
// - Source slice remains valid
fn main() {
// Inline capacity = 2
type MySmallVec = SmallVec<[String; 2]>;
// from_vec with Vec that fits inline
let small_vec = vec!["a".to_string(), "b".to_string()];
let sv = MySmallVec::from_vec(small_vec);
println!("Small inline: is_inline = {}", sv.is_inline());
// from_vec with Vec that needs heap
let large_vec: Vec<String> = (0..100)
.map(|i| format!("item_{}", i))
.collect();
let sv = MySmallVec::from_vec(large_vec);
println!("Large heap: is_inline = {}", sv.is_inline());
// from_slice always copies
let slice_data: Vec<String> = (0..100)
.map(|i| format!("item_{}", i))
.collect();
// Would require cloning 100 strings if we used from_slice
}The implementation optimizes from_vec to avoid copying when possible.
use smallvec::SmallVec;
fn main() {
// SmallVec can be converted back to Vec
// Case 1: Inline SmallVec -> Vec
let sv: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
let vec = sv.into_vec();
// Allocates new Vec, moves elements from inline
// Case 2: Heap SmallVec -> Vec
let sv: SmallVec<[i32; 4]> = (0..100).collect();
let vec = sv.into_vec();
// If heap capacity is "close" to length, reuses allocation
// Otherwise allocates new Vec
// Round-trip: Vec -> SmallVec -> Vec
let original = vec![1, 2, 3, 4, 5];
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(original);
let recovered = sv.into_vec();
// If original was large enough for heap:
// - from_vec: takes Vec's allocation
// - into_vec: returns allocation (same one)
// Very efficient round-trip!
}Round-tripping through SmallVec can preserve heap allocations.
use smallvec::{SmallVec, smallvec};
// When a function might return small or large data,
// SmallVec allows optimization for the common small case
fn parse_numbers(input: &str) -> SmallVec<[i32; 16]> {
// Most inputs have <= 16 numbers (inline)
// Rarely, inputs are larger (heap)
let numbers: Vec<i32> = input
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
// Option 1: from_vec (owns the data)
SmallVec::from_vec(numbers)
// Option 2: from_slice (if we borrowed the data)
// Would be: SmallVec::from_slice(&numbers)
// But that requires Clone and copies elements
}
fn parse_numbers_borrowed(input: &str) -> SmallVec<[i32; 16]> {
// If we don't need to keep a Vec around:
let numbers: Vec<i32> = input
.split_whitespace()
.filter_map(|s| s.parse().ok())
.collect();
// from_vec is correct here - we own the Vec
SmallVec::from_vec(numbers)
}
fn main() {
let small = parse_numbers("1 2 3 4 5");
let large = parse_numbers(&"1 ".repeat(100));
println!("Small inline: {}", small.is_inline());
println!("Large heap: {}", !large.is_inline());
}from_vec is idiomatic when you already own a Vec.
use smallvec::SmallVec;
// Integration with APIs that return Vec
fn receive_large_data() -> Vec<u8> {
// Imagine this comes from network or file
(0..10000).collect()
}
fn process_data() -> SmallVec<[u8; 256]> {
let vec = receive_large_data();
// from_vec takes ownership without copying
SmallVec::from_vec(vec)
// If we used from_slice:
// let sv = SmallVec::from_slice(&vec);
// - Vec would still be allocated
// - All 10000 bytes would be copied
// - Double memory usage temporarily
}
// Integration with APIs that provide slices
fn process_borrowed_data(data: &[u8]) -> SmallVec<[u8; 256]> {
// When data is borrowed, from_slice is appropriate
SmallVec::from_slice(data)
}
fn main() {
let processed = process_data();
println!("Processed {} bytes", processed.len());
}Use from_vec when you own; use from_slice when you borrow.
use smallvec::SmallVec;
fn main() {
// Vec with excess capacity
let mut vec: Vec<i32> = Vec::with_capacity(1000);
vec.extend(0..100); // 100 elements, capacity 1000
// from_vec preserves the capacity when using heap
let sv: SmallVec<[i32; 4]> = SmallVec::from_vec(vec);
// sv now has capacity 1000 (the Vec's capacity)
// This is important for subsequent pushes
println!("Capacity after from_vec: {}", sv.capacity());
// from_slice doesn't have capacity info, allocates for exact size
let slice: &[i32] = &(0..100).collect::<Vec<_>>()[..];
let sv2: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
// Capacity is exactly 100 (no extra)
println!("Capacity from from_slice: {}", sv2.capacity());
}from_vec preserves capacity; from_slice allocates for exact size.
// Summary of key differences:
fn comparison_table() {
// from_vec(Vec) from_slice(&[T])
// -------------------------------------------------------
// Ownership Takes ownership Borrows
// Clone required? No Yes
// Copies elements? Only if inline Always
// Reuses allocation? Yes (if heap) No (new allocation)
// Capacity preserved? Yes No (exact size)
// Zero-copy? Yes (heap case) Never
// Source valid? No (moved) Yes (borrowed)
}Decision matrix:
| Situation | Recommended Method |
|-----------|-------------------|
| Have a Vec to transfer | from_vec |
| Have a borrowed slice | from_slice |
| Type doesn't implement Clone | from_vec only |
| Want to preserve capacity | from_vec |
| Minimize allocations | from_vec |
| Keep source data | from_slice |
| Small inline data | Either (similar cost) |
Key insight: SmallVec::from_vec and from_slice represent two fundamental patterns in Rust's ownership system: the difference between taking ownership and borrowing. from_vec is the "zero-cost" path when you already own a Vecāit either moves elements inline (if they fit) or takes ownership of the entire heap allocation (if they don't), never requiring Clone and never copying elements on the heap. from_slice is the "defensive copy" path for borrowed dataāit must copy every element and requires Clone, but leaves the source intact. The performance difference is dramatic for large collections: from_vec on a heap-allocated Vec is essentially a pointer transfer (O(1)), while from_slice on the same data would clone every element (O(n)). This mirrors the broader Rust philosophy that ownership enables optimization: when you own data, you can move it without copying; when you borrow, you must copy if you need your own version. For SmallVec specifically, this interacts with the inline optimizationāwhen data fits inline, both methods copy elements, but from_vec can still avoid the Clone bound by using move. The practical guidance is simple: use from_vec when you have a Vec to give (it's strictly better or equal), use from_slice when you only have a borrowed reference (there's no alternative).