What are the trade-offs between smallvec::SmallVec::from_buf and from_slice for initializing small vectors?

from_buf takes ownership of an existing array to use as the inline buffer without copying, while from_slice copies data from a slice into a newly allocated SmallVec. The key trade-off is between zero-copy efficiency (from_buf) and convenience for any slice source (from_slice).

SmallVec Fundamentals

use smallvec::SmallVec;
 
fn smallvec_basics() {
    // SmallVec stores small arrays inline, spills to heap for large data
    // A SmallVec<[i32; 4]> stores up to 4 elements inline
    
    // When data fits inline: no heap allocation
    let small: SmallVec<[i32; 4]> = SmallVec::from_buf([1, 2, 3, 4]);
    // All 4 elements stored inline, no heap allocation
    
    // When data exceeds inline capacity: spills to heap
    let large: SmallVec<[i32; 2]> = smallvec
![1, 2, 3, 4];
    // Capacity is 2, so data spills to heap
}

SmallVec stores a fixed number of elements inline, with heap fallback for larger data.

The from_slice Method

use smallvec::SmallVec;
 
fn from_slice_example() {
    // from_slice copies data from any slice into SmallVec
    
    let data = [1, 2, 3, 4];
    
    // Creates new SmallVec by copying slice data
    let vec: SmallVec<[i32; 4]> = SmallVec::from_slice(&data);
    
    // Original data unchanged
    assert_eq!(data, [1, 2, 3, 4]);
    
    // Works with any slice source
    let slice: &[i32] = &[5, 6, 7];
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
    
    // Works with Vec slices
    let v = vec
![10, 20, 30];
    let vec3: SmallVec<[i32; 4]> = SmallVec::from_slice(&v);
}

from_slice copies data from any slice source into a new SmallVec.

The from_buf Method

use smallvec::SmallVec;
 
fn from_buf_example() {
    // from_buf takes ownership of an existing array
    // Uses it directly as the inline buffer - no copy
    
    let data = [1, 2, 3, 4];
    
    // Takes ownership of the array, uses as inline storage
    let vec: SmallVec<[i32; 4]> = SmallVec::from_buf(data);
    
    // data is moved, can't use it anymore
    // vec now owns that exact memory
    
    // Zero copy - the array becomes the inline buffer
}

from_buf adopts an existing array as the inline buffer without copying.

Memory Layout Comparison

use smallvec::SmallVec;
 
fn memory_layout() {
    // from_slice: creates new storage, copies data in
    let data = [1, 2, 3, 4];
    let vec1: SmallVec<[i32; 4]> = SmallVec::from_slice(&data);
    // Memory: data exists, vec1 has its own copy
    
    // from_buf: adopts existing array as storage
    let data2 = [5, 6, 7, 8];
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_buf(data2);
    // Memory: data2's storage becomes vec2's inline buffer
    // No additional allocation, no copy
    
    // ┌─────────────────────────────────────────────────────────────────────────┐
    // │ Method      │ Source     │ Copy?    │ Source Usable After?            │
    // ├─────────────────────────────────────────────────────────────────────────┤
    // │ from_slice  │ &[T]       │ Yes      │ Yes (slice unchanged)            │
    // │ from_buf    │ [T; N]     │ No       │ No (array moved)                 │
    // └─────────────────────────────────────────────────────────────────────────┘
}

from_slice copies; from_buf takes ownership without copying.

Copy Overhead Comparison

use smallvec::SmallVec;
 
fn copy_overhead() {
    // For small arrays, copy overhead is minimal
    // For large arrays, from_buf avoids the copy
    
    // Small array - copy is cheap
    let small = [1, 2, 3];
    let vec1: SmallVec<[i32; 4]> = SmallVec::from_slice(&small);
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_buf(small);
    // Difference negligible for small data
    
    // Larger array - copy overhead matters
    let large: [u8; 1024] = [42; 1024];
    
    // from_slice: copies 1024 bytes
    let vec3: SmallVec<[u8; 1024]> = SmallVec::from_slice(&large);
    
    // from_buf: takes ownership, no copy
    let vec4: SmallVec<[u8; 1024]> = SmallVec::from_buf(large);
    // More efficient for large inline arrays
}

from_buf avoids copy overhead, especially significant for larger inline arrays.

Source Requirements

use smallvec::SmallVec;
 
fn source_requirements() {
    // from_slice: accepts any slice
    let vec_data = vec
![1, 2, 3, 4, 5];
    let arr_data = [10, 20, 30, 40, 50];
    let slice: &[i32] = &[100, 200, 300];
    
    // All work with from_slice
    let v1: SmallVec<[i32; 8]> = SmallVec::from_slice(&vec_data);
    let v2: SmallVec<[i32; 8]> = SmallVec::from_slice(&arr_data);
    let v3: SmallVec<[i32; 8]> = SmallVec::from_slice(slice);
    
    // from_buf: requires exactly-sized array matching inline capacity
    let arr = [1, 2, 3, 4];
    let v4: SmallVec<[i32; 4]> = SmallVec::from_buf(arr);
    
    // Won't compile: size mismatch
    // let arr5 = [1, 2, 3, 4, 5];
    // let v5: SmallVec<[i32; 4]> = SmallVec::from_buf(arr5);
    // Error: expected array of 4 elements, found 5
    
    // Must match: from_buf([T; N]) -> SmallVec<[T; N]>
}

from_buf requires an array matching the SmallVec's inline capacity exactly.

Type Signature Differences

use smallvec::SmallVec;
 
fn signatures() {
    // from_slice signature:
    // fn from_slice(slice: &[T]) -> Self
    // where
    //     T: Clone,
    //     A: Array<Item = T>
    
    // Requirements:
    // - T must implement Clone (for copying)
    // - Slice can be any length
    // - Creates SmallVec with capacity from A
    
    // from_buf signature:
    // fn from_buf(buf: A) -> Self
    // where
    //     A: Array
    
    // Requirements:
    // - Takes array A directly
    // - No Clone bound needed (no copying)
    // - Array size defines SmallVec capacity
    
    // Key difference: from_slice requires Clone, from_buf does not
}

from_slice requires Clone; from_buf does not because it takes ownership.

Clone Requirement Implications

use smallvec::SmallVec;
 
struct NonClone(i32);
 
fn clone_requirement() {
    let items = [NonClone(1), NonClone(2), NonClone(3)];
    
    // from_buf works: no Clone needed
    let vec: SmallVec<[NonClone; 3]> = SmallVec::from_buf(items);
    
    // from_slice won't compile: requires Clone
    // let vec2: SmallVec<[NonClone; 3]> = SmallVec::from_slice(&items);
    // Error: NonClone doesn't implement Clone
}

from_buf works with non-Clone types; from_slice requires Clone.

Length Semantics

use smallvec::SmallVec;
 
fn length_semantics() {
    // from_buf: length equals array length
    let arr = [1, 2, 3, 4];
    let vec1: SmallVec<[i32; 4]> = SmallVec::from_buf(arr);
    assert_eq!(vec1.len(), 4);  // Full capacity used
    
    // from_slice: length equals slice length
    let slice = &[10, 20][..];
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
    assert_eq!(vec2.len(), 2);  // Only 2 elements
    
    // from_buf always fills the inline capacity
    // from_slice can have fewer elements than capacity
}

from_buf uses the full array; from_slice can have fewer elements than capacity.

Working with Partial Buffers

use smallvec::SmallVec;
 
fn partial_buffers() {
    // from_buf takes the entire array
    let full = [0; 100];
    let vec: SmallVec<[i32; 100]> = SmallVec::from_buf(full);
    // len = 100, all zeros
    
    // If you want fewer elements, you need different approach
    // Option 1: Use from_slice with partial data
    let partial = &[1, 2, 3][..];
    let vec2: SmallVec<[i32; 100]> = SmallVec::from_slice(partial);
    assert_eq!(vec2.len(), 3);
    
    // Option 2: Use from_buf and then truncate
    let mut vec3: SmallVec<[i32; 100]> = SmallVec::from_buf([0; 100]);
    vec3.truncate(10);
    assert_eq!(vec3.len(), 10);
    
    // Option 3: Use from_buf_with_length
    // This takes an array and a length
    let arr = [1, 2, 3, 4, 5, 6, 7, 8];
    let vec4: SmallVec<[i32; 8]> = SmallVec::from_buf_with_length(arr, 3);
    assert_eq!(vec4.len(), 3);
    // arr contains [1, 2, 3, ...], but vec4 only reports 3 elements
}

from_buf fills the array; use from_buf_with_length or from_slice for partial data.

The from_buf_with_length Variant

use smallvec::SmallVec;
 
fn from_buf_with_length_example() {
    // from_buf_with_length: takes ownership with explicit length
    let arr = [1, 2, 3, 4, 5, 6, 7, 8];
    
    // Create SmallVec with only 3 elements from the array
    let vec: SmallVec<[i32; 8]> = SmallVec::from_buf_with_length(arr, 3);
    
    assert_eq!(vec.len(), 3);
    assert_eq!(&vec[..], &[1, 2, 3]);
    
    // Remaining array elements are still there but not "counted"
    // The array memory is still valid, just length is capped
    
    // This combines zero-copy with partial data
}

from_buf_with_length provides zero-copy initialization with fewer elements than capacity.

Performance Comparison

use smallvec::SmallVec;
 
fn performance_comparison() {
    // ┌─────────────────────────────────────────────────────────────────────────┐
    // │ Scenario                  │ from_slice      │ from_buf              │
    // ├─────────────────────────────────────────────────────────────────────────┤
    // │ Data already in array    │ Copy needed     │ Zero copy             │
    // │ Data in slice            │ Copy needed     │ N/A (needs array)     │
    // │ Non-Clone types          │ Won't compile   │ Works                 │
    // │ Partial data             │ Natural         │ Use from_buf_with_len │
    // │ Small arrays             │ Fast            │ Slightly faster      │
    // │ Large inline arrays       │ Copy overhead   │ Significant win      │
    // │ Unknown size at compile   │ Works           │ Needs exact size     │
    // └─────────────────────────────────────────────────────────────────────────┘
    
    // Benchmark: large inline array
    let large: [u8; 4096] = [42; 4096];
    
    // from_slice: copies 4096 bytes
    // from_buf: takes ownership, no copy
    
    // For large inline capacities, from_buf is significantly faster
}

from_buf has performance advantages for large inline arrays.

Use Cases for from_slice

use smallvec::SmallVec;
 
fn from_slice_use_cases() {
    // 1. Converting from external slice references
    fn process_slice(data: &[i32]) -> SmallVec<[i32; 16]> {
        SmallVec::from_slice(data)
    }
    
    // 2. Creating from Vec data
    fn from_vec(vec: Vec<i32>) -> SmallVec<[i32; 16]> {
        SmallVec::from_slice(&vec)
    }
    
    // 3. Partial data (fewer elements than capacity)
    let partial = &[1, 2, 3];
    let vec: SmallVec<[i32; 16]> = SmallVec::from_slice(partial);
    // Capacity 16, but only 3 elements
    
    // 4. When you don't own the data
    fn borrow_and_copy(data: &[String]) -> SmallVec<[String; 4]> {
        SmallVec::from_slice(data)  // Clones Strings
    }
    
    // 5. Runtime-determined size
    fn runtime_size(data: &[i32]) -> SmallVec<[i32; 32]> {
        // Works regardless of input size
        SmallVec::from_slice(data)
    }
}

Use from_slice for borrowed data, partial data, or when you don't own the source.

Use Cases for from_buf

use smallvec::SmallVec;
 
fn from_buf_use_cases() {
    // 1. Zero-copy from owned array
    let data = [1, 2, 3, 4];
    let vec: SmallVec<[i32; 4]> = SmallVec::from_buf(data);
    
    // 2. Non-Clone types
    struct Resource { handle: i32 }
    let resources = [Resource { handle: 1 }, Resource { handle: 2 }];
    let vec: SmallVec<[Resource; 2]> = SmallVec::from_buf(resources);
    
    // 3. Large inline arrays where copy is expensive
    let large: [u8; 4096] = [0; 4096];
    let vec: SmallVec<[u8; 4096]> = SmallVec::from_buf(large);
    // Avoids copying 4096 bytes
    
    // 4. When you have exactly the right size array
    fn exactly_sized() -> SmallVec<[f64; 8]> {
        let arr = [0.0; 8];
        SmallVec::from_buf(arr)
    }
    
    // 5. Stack-allocated working buffers
    fn working_buffer() {
        let buffer: [u8; 256] = [0; 256];
        let mut vec: SmallVec<[u8; 256]> = SmallVec::from_buf(buffer);
        // Use vec...
        // No heap allocation for first 256 bytes
    }
}

Use from_buf for zero-copy, non-Clone types, or large inline arrays.

Common Patterns

use smallvec::SmallVec;
 
fn common_patterns() {
    // Pattern 1: Initialize from literal
    let vec1: SmallVec<[i32; 4]> = SmallVec::from_buf([1, 2, 3, 4]);
    // Zero copy, array is inline
    
    // Pattern 2: Initialize from slice literal
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3, 4]);
    // Copies slice into SmallVec
    
    // Pattern 3: Using smallvec! macro (most ergonomic)
    let vec3: SmallVec<[i32; 4]> = smallvec
![1, 2, 3, 4];
    // Internally uses from_slice for array literals
    
    // Pattern 4: Empty with capacity
    let mut vec4: SmallVec<[i32; 4]> = SmallVec::new();
    vec4.push(1);
    vec4.push(2);
    // Capacity 4, len 2
}

The smallvec! macro is often the most ergonomic choice for literals.

Interaction with Heap Spill

use smallvec::SmallVec;
 
fn heap_spill() {
    // SmallVec spills to heap when data exceeds inline capacity
    
    // from_slice: handles spill automatically
    let data = [1, 2, 3, 4, 5, 6, 7, 8];
    let vec: SmallVec<[i32; 4]> = SmallVec::from_slice(&data);
    // Inline capacity is 4, data has 8 elements
    // vec spills to heap, all 8 elements stored
    
    // from_buf: array must match inline capacity
    let arr4 = [1, 2, 3, 4];
    let vec2: SmallVec<[i32; 4]> = SmallVec::from_buf(arr4);
    // Exact match, all inline, no heap
    
    // Can't use from_buf with different size
    // let arr8 = [1, 2, 3, 4, 5, 6, 7, 8];
    // let vec3: SmallVec<[i32; 4]> = SmallVec::from_buf(arr8);
    // Error: array size must match SmallVec capacity
}

from_slice handles heap spill; from_buf requires exact capacity match.

Complete Comparison

use smallvec::SmallVec;
 
fn complete_comparison() {
    // ┌─────────────────────────────────────────────────────────────────────────┐
    // │ Aspect               │ from_slice             │ from_buf              │
    // ├─────────────────────────────────────────────────────────────────────────┤
    // │ Input type           │ &[T]                   │ [T; N]                │
    // │ Ownership            │ Borrows, copies        │ Takes ownership       │
    // │ Copy required        │ Yes (Clone bound)      │ No                    │
    // │ Non-Clone types      │ Won't compile          │ Works                 │
    // │ Size flexibility     │ Any slice length       │ Exact array size      │
    // │ Partial data         │ Natural                │ Use from_buf_with_len │
    // │ Performance          │ Copy overhead          │ Zero copy             │
    // │ Use after            │ Source usable          │ Source moved          │
    // │ Heap spill           │ Automatic              │ N/A (fixed size)      │
    // │ Common use           │ External data          │ Owned arrays          │
    // └─────────────────────────────────────────────────────────────────────────┘
    
    // Decision guide:
    // - Have a slice? Use from_slice
    // - Have an owned array matching capacity? Use from_buf
    // - Non-Clone type? Must use from_buf
    // - Large inline array? from_buf is faster
    // - Convenience? from_slice is more flexible
}

Summary

use smallvec::SmallVec;
 
fn summary() {
    // Key decision points:
    
    // Use from_slice when:
    // - Data comes from a borrowed slice
    // - You need flexibility in data size
    // - Data has fewer elements than capacity
    // - You don't own the source data
    
    // Use from_buf when:
    // - You own an array matching SmallVec capacity
    // - Zero-copy is important (large arrays)
    // - Type doesn't implement Clone
    // - Maximum performance is needed
    
    // Use from_buf_with_length when:
    // - You own an array but want partial data
    // - Need zero-copy with fewer elements than capacity
    
    // All three methods create valid SmallVecs;
    // the choice depends on ownership, performance, and flexibility needs.
}

Key insight: from_buf and from_slice serve different ownership scenarios. from_buf is a zero-copy operation that takes ownership of an existing array, making it ideal for non-Clone types and large inline arrays where copying is expensive. from_slice copies from any slice source, providing flexibility for borrowed data and variable sizes at the cost of a copy. Choose from_buf when you have an owned array matching the capacity and want maximum performance; choose from_slice for convenience with borrowed data or when you don't own the source array.