What is the difference between 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.

Basic Usage Comparison

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.

Ownership and Clone Requirements

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.

Inline Capacity Behavior

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.

Memory Allocation Analysis

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.

Performance Comparison

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.

When Elements Fit Inline

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.

Heap Allocation Transfer

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.

SmallVec Implementation Details

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.

Converting Back to Vec

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 Case: Function Returning SmallVec

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 Case: Zero-Copy Integration

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.

Capacity Preservation

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.

Comparison Summary Table

// 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)
}

Synthesis

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).