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.
