- Rust 99.1%
- Shell 0.9%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| benches | ||
| fuzz | ||
| scripts | ||
| src | ||
| tests | ||
| .gitignore | ||
| Cargo.toml | ||
| LICENSE-APACHE | ||
| LICENSE-MIT | ||
| README.md | ||
SmallVec
⚠️ Note: This is smallvec 2.0.0-alpha.12, a complete rewrite using const generics. For the stable 1.x version, see the v1 branch.
SmallVec is a vector implementation that stores elements inline on the stack when the count is small, automatically spilling to the heap only when necessary. This provides cache-locality benefits and reduces allocator traffic for typical workloads.
Table of Contents
- Overview
- Quick Start
- Architecture
- API Reference
- Performance Characteristics
- Feature Flags
- Advanced Usage
- Comparison with Vec
Overview
SmallVec uses a union-based storage strategy with a tagged length field to seamlessly transition between stack and heap storage:
flowchart TB
classDef stack fill:#d4edda,stroke:#28a745,stroke-width:2px
classDef heap fill:#d1ecf1,stroke:#17a2b8,stroke-width:2px
classDef meta fill:#fff3cd,stroke:#ffc107,stroke-width:2px
classDef encoding fill:#f8d7da,stroke:#dc3545,stroke-width:2px
subgraph SV["SmallVec<T, N>"]
direction TB
L["len: TaggedLen<T>"]
R["raw: RawSmallVec<T, N>"]
M["_marker: PhantomData<T>"]
end
subgraph RL["RawSmallVec Union"]
direction TB
I["inline: ManuallyDrop<MaybeUninit<[T; N]>>"]
H["heap: (NonNull<T>, usize)"]
end
subgraph TL["TaggedLen Encoding"]
direction TB
B["Bit 0: on_heap flag"]
V["Bits 1..: length << 1"]
end
R --> I
R --> H
L --> B
L --> V
class I,M stack
class H heap
class L,R meta
class B,V encoding
Memory Layout Visualization
flowchart TB
classDef stack fill:#d4edda,stroke:#28a745,stroke-width:2px
classDef heap fill:#d1ecf1,stroke:#17a2b8,stroke-width:2px
classDef meta fill:#fff3cd,stroke:#ffc107,stroke-width:2px
classDef union fill:#e2e3e5,stroke:#6c757d,stroke-width:2px
subgraph InlineState["INLINE State (len ≤ N)"]
direction TB
IL["len: usize (on_heap=false)"]
subgraph InlineUnion["RawSmallVec"]
II["inline: [T; N] as MaybeUninit"]
end
IM["_marker: PhantomData<T>"]
end
subgraph HeapState["HEAP State (len > N)"]
direction TB
HL["len: usize (on_heap=true)"]
subgraph HeapUnion["RawSmallVec"]
HH1["heap.ptr: NonNull<T>"]
HH2["heap.cap: usize"]
end
HM["_marker: PhantomData<T>"]
end
subgraph HeapAlloc["Heap Allocation"]
direction TB
HE1["Element 0"]
HE2["Element 1"]
HE3["..."]
HE4["Element len-1"]
end
HH1 -.->|points to| HeapAlloc
class II,IL,IM stack
class HH1,HH2,HL,HM heap
class InlineUnion,HeapUnion union
Quick Start
Add to your Cargo.toml:
[dependencies]
smallvec = "2.0.0-alpha.12"
Basic usage:
use smallvec::{SmallVec, smallvec};
// Create a SmallVec with inline capacity for 4 items
let mut v: SmallVec<i32, 4> = smallvec![1, 2, 3, 4];
// Access elements like a regular slice
assert_eq!(v[0], 1);
v[0] = 10;
// Push elements - stays inline if ≤ 4 elements
v.push(5); // Now spilled to heap!
// Automatic conversion back to Vec
let vec: Vec<i32> = v.into_vec();
Architecture
State Transitions
stateDiagram-v2
[*] --> Empty
Empty --> Inline : push() while len < N
Inline --> Inline : push() while len < N
Inline --> Spilled : push() when len == N
Spilled --> Spilled : push() / grow()
Spilled --> Inline : shrink_to_fit()<br/>when len ≤ N
Empty --> [*] : drop
Inline --> [*] : drop elements
Spilled --> [*] : drop elements + dealloc
note right of Inline
Stack storage
No allocation
Better cache locality
end note
note right of Spilled
Heap storage
Dynamic capacity
Standard Vec-like
end note
Method Categories
mindmap
root((SmallVec API))
Construction
["new()"]
["with_capacity()"]
["from_buf()"]
["from_vec()"]
["smallvec![]"]
["smallvec_inline![]"]
ElementAccess["Element Access"]
["Indexing []"]
["get()"]
["first() / last()"]
["as_slice()"]
["as_mut_slice()"]
Mutation
["push()"]
["pop()"]
["insert()"]
["remove()"]
["swap_remove()"]
BulkOperations["Bulk Operations"]
["extend()"]
["append()"]
["split_off()"]
["drain()"]
["splice()"]
["clear()"]
Capacity
["capacity()"]
["reserve()"]
["shrink_to_fit()"]
["grow()"]
["spilled()"]
Conversion
["into_vec()"]
["into_inner()"]
["into_boxed_slice()"]
["from_raw_parts()"]
Filtering
["retain()"]
["dedup()"]
["dedup_by()"]
["extract_if (unstable)"]
API Reference
Construction
SmallVec::new()
Creates an empty SmallVec with const-generic inline capacity.
use smallvec::SmallVec;
let v: SmallVec<i32, 8> = SmallVec::new();
assert!(v.is_empty());
assert!(!v.spilled());
assert_eq!(v.capacity(), 8);
SmallVec::with_capacity()
Creates with at least the specified capacity. May allocate on heap if capacity > N.
use smallvec::SmallVec;
// Fits inline
let v: SmallVec<u8, 4> = SmallVec::with_capacity(2);
assert!(!v.spilled());
assert_eq!(v.capacity(), 4);
// Requires heap
let v: SmallVec<u8, 4> = SmallVec::with_capacity(100);
assert!(v.spilled());
assert_eq!(v.capacity(), 100);
SmallVec::from_buf()
Creates from a fixed-size array. The array must fit within inline capacity.
use smallvec::SmallVec;
// Exact fit
let v = SmallVec::<i32, 4>::from_buf([1, 2, 3, 4]);
assert_eq!(v.len(), 4);
assert!(!v.spilled());
// Smaller than capacity
let v = SmallVec::<i32, 8>::from_buf([1, 2]);
assert_eq!(v.len(), 2);
assert!(!v.spilled());
SmallVec::from_buf_and_len()
Creates from buffer with explicit length (drops uninitialized elements).
use smallvec::SmallVec;
let buf = [1, 2, 3, 4, 0, 0, 0, 0];
let v = SmallVec::<i32, 8>::from_buf_and_len(buf, 4);
assert_eq!(&*v, &[1, 2, 3, 4]);
SmallVec::from_vec()
Consumes a Vec, reusing its heap allocation if possible.
use smallvec::SmallVec;
// Small vec - may convert to inline
let vec = vec![1, 2, 3];
let sv: SmallVec<i32, 8> = SmallVec::from_vec(vec);
// Large vec - stays on heap
let vec = vec![1; 1000];
let sv: SmallVec<i32, 8> = SmallVec::from_vec(vec);
assert!(sv.spilled());
Macros
smallvec![]
Vec-like macro with automatic optimization.
use smallvec::{smallvec, SmallVec};
// Empty
let v: SmallVec<i32, 4> = smallvec![];
// From elements
let v = smallvec![1, 2, 3];
assert_eq!(v.len(), 3);
// From element + count (clone)
let v = smallvec![0; 100];
assert_eq!(v.len(), 100);
// Large literal goes directly to Vec
let v: SmallVec<i32, 2> = smallvec![1, 2, 3, 4, 5];
assert!(v.spilled()); // Macro detected overflow
smallvec_inline![]
Guaranteed inline storage macro.
use smallvec::smallvec_inline;
// Const-friendly - evaluated at compile time
const V: smallvec::SmallVec<i32, 2> = {
use smallvec::SmallVec;
smallvec_inline![1, 2]
};
// Repeat pattern
let v = smallvec_inline![42; 4]; // [42, 42, 42, 42]
Element Operations
push(), pop(), pop_if()
use smallvec::SmallVec;
let mut v = SmallVec::<i32, 2>::new();
// Push until spill
v.push(1);
v.push(2);
assert!(!v.spilled());
v.push(3); // Spills to heap
assert!(v.spilled());
// Pop
assert_eq!(v.pop(), Some(3));
// Conditional pop
v.push(100);
let big = v.pop_if(|x| *x > 50);
assert_eq!(big, Some(100));
insert(), remove(), swap_remove()
use smallvec::SmallVec;
let mut v = SmallVec::<i32, 8>::from([1, 2, 4, 5]);
// Insert shifts elements
v.insert(2, 3);
assert_eq!(&*v, &[1, 2, 3, 4, 5]);
// Remove shifts elements back
let removed = v.remove(2);
assert_eq!(removed, 3);
assert_eq!(&*v, &[1, 2, 4, 5]);
// swap_remove is O(1) - swaps with last
let swapped = v.swap_remove(1); // swaps 2 with 5
assert_eq!(swapped, 2);
assert_eq!(&*v, &[1, 5, 4]);
Bulk Operations
extend(), append()
use smallvec::SmallVec;
let mut v = SmallVec::<i32, 4>::new();
v.extend([1, 2, 3]);
let mut other = SmallVec::<i32, 2>::from([4, 5]);
v.append(&mut other);
assert_eq!(&*v, &[1, 2, 3, 4, 5]);
assert!(other.is_empty());
split_off()
use smallvec::SmallVec;
let mut v = SmallVec::<i32, 4>::from([1, 2, 3, 4, 5, 6]);
let v2 = v.split_off(3);
assert_eq!(&*v, &[1, 2, 3]);
assert_eq!(&*v2, &[4, 5, 6]);
drain() and splice()
use smallvec::{smallvec, SmallVec};
// Drain removes and yields elements
let mut v: SmallVec<i32, 4> = smallvec![1, 2, 3, 4, 5];
let drained: SmallVec<i32, 4> = v.drain(1..4).collect();
assert_eq!(&*drained, &[2, 3, 4]);
assert_eq!(&*v, &[1, 5]);
// Splice replaces a range
let mut v: SmallVec<i32, 4> = smallvec![1, 2, 3, 4, 5];
let removed: SmallVec<i32, 4> = v.splice(1..4, [10, 20]).collect();
assert_eq!(&*removed, &[2, 3, 4]);
assert_eq!(&*v, &[1, 10, 20, 5]);
Filtering and Deduplication
retain() and extract_if
use smallvec::SmallVec;
// retain keeps elements matching predicate
let mut v = SmallVec::<i32, 8>::from([1, 2, 3, 4, 5]);
v.retain(|&x| x % 2 == 0);
assert_eq!(&*v, &[2, 4]);
// extract_if (requires feature) yields removed elements
#[cfg(feature = "extract_if")]
{
let mut v = SmallVec::<i32, 8>::from([1, 2, 3, 4, 5, 6]);
let evens: SmallVec<i32, 8> = v.extract_if(.., |x| *x % 2 == 0).collect();
assert_eq!(&*evens, &[2, 4, 6]);
assert_eq!(&*v, &[1, 3, 5]);
}
dedup() variants
use smallvec::SmallVec;
// Simple dedup
let mut v = SmallVec::<i32, 8>::from([1, 1, 2, 2, 3, 3, 3]);
v.dedup();
assert_eq!(&*v, &[1, 2, 3]);
// dedup_by_key
let mut v = SmallVec::<(i32, String), 8>::from([
(1, "a".to_string()),
(1, "b".to_string()),
(2, "c".to_string()),
]);
v.dedup_by_key(|x| x.0);
assert_eq!(v.len(), 2);
// dedup_by with custom comparison
let mut v = SmallVec::<i32, 8>::from([1, 2, 3, 4, 5]);
v.dedup_by(|a, b| (a - b).abs() <= 1);
// Keeps 1, removes 2 (|2-1|<=1), keeps 3, removes 4 (|4-3|<=1), keeps 5
assert_eq!(&*v, &[1, 3, 5]);
Capacity Management
reserve(), shrink_to_fit(), grow()
use smallvec::SmallVec;
// Reserve space
let mut v = SmallVec::<i32, 4>::new();
v.reserve(100); // Forces heap allocation
assert!(v.spilled());
// Shrink back to inline if possible
v.extend(0..3);
v.shrink_to_fit();
assert!(!v.spilled()); // Moved back to stack!
// Manual grow
let mut v = SmallVec::<i32, 2>::new();
v.grow(10); // Explicitly set capacity
assert!(v.spilled());
assert_eq!(v.capacity(), 10);
Conversion
into_vec(), into_inner(), into_boxed_slice()
use smallvec::SmallVec;
// into_vec - converts to Vec
let v = SmallVec::<i32, 4>::from([1, 2, 3]);
let vec: Vec<i32> = v.into_vec();
assert_eq!(vec, vec![1, 2, 3]);
// into_inner - get array if exactly right size
let v = SmallVec::<i32, 3>::from([1, 2, 3]);
let arr: [i32; 3] = v.into_inner().unwrap();
assert_eq!(arr, [1, 2, 3]);
// into_inner returns Err if wrong size
let v = SmallVec::<i32, 3>::from([1, 2]);
let result = v.into_inner();
assert!(result.is_err());
// into_boxed_slice
let v = SmallVec::<i32, 4>::from([1, 2, 3]);
let boxed: Box<[i32]> = v.into_boxed_slice();
Advanced: from_raw_parts
use smallvec::{smallvec, SmallVec};
let mut v: SmallVec<i32, 1> = smallvec![1, 2, 3];
// Extract raw parts (only safe for spilled vectors!)
let ptr = v.as_mut_ptr();
let len = v.len();
let cap = v.capacity();
let spilled = v.spilled();
unsafe {
std::mem::forget(v);
assert!(spilled);
// Write new data
std::ptr::write(ptr.add(0), 10);
std::ptr::write(ptr.add(1), 20);
std::ptr::write(ptr.add(2), 30);
// Rebuild SmallVec with different inline capacity
let rebuilt = SmallVec::<i32, 2>::from_raw_parts(ptr, len, cap);
assert_eq!(&*rebuilt, &[10, 20, 30]);
}
Time Complexity
| Operation | Inline | Heap | Notes |
|---|---|---|---|
push (amortized) |
O(1) | O(1) | Inline may trigger spill once |
push (worst) |
O(N) | O(N) | When spilling/reallocating |
pop |
O(1) | O(1) | |
insert |
O(N) | O(N) | |
remove |
O(N) | O(N) | |
swap_remove |
O(1) | O(1) | |
get/[] |
O(1) | O(1) | Inline slightly faster (no indirection) |
shrink_to_fit |
O(N) | O(N) | May unspill (move to stack) |
Performance Characteristics
SmallVec provides two distinct performance profiles depending on whether data fits inline or spills to heap.
Inline Storage: Cache-Hot Performance
When len <= N, SmallVec stores elements directly in the struct's inline buffer on the stack. This provides:
- Zero allocation cost: No heap allocator calls, deterministic performance
- Cache locality: Elements live in the same cache line as the struct itself
- No pointer indirection: Direct indexing via
as_ptr_inline():
// From src/lib.rs:209-214
const fn as_ptr_inline(&self) -> *const T {
// SAFETY: We only get a pointer, no reads
addr_of!(self.inline) as *const T
}
The inline pointer is computed as the address of the union's inline member. No heap pointer chasing means single-cache-line access patterns that prefetchers handle well.
Heap Storage: Standard Vec Performance
When len > N, SmallVec behaves identically to Vec<T>:
// From src/lib.rs:226-228
const unsafe fn as_ptr_heap(&self) -> *const T {
unsafe { self.heap.0.as_ptr() }
}
Access requires loading the heap pointer from the union, then dereferencing. This matches Vec's double-indirection pattern with identical cache characteristics.
Allocation Strategy: Growth and Spilling
When an inline vector needs more capacity, try_grow_raw handles the spill:
// From src/lib.rs:242-292 (simplified)
unsafe fn try_grow_raw(&mut self, len: TaggedLen<T>, new_capacity: usize)
-> Result<(), CollectionAllocErr>
{
let was_on_heap = len.on_heap();
let ptr = if was_on_heap {
unsafe { self.as_mut_ptr_heap() }
} else {
self.as_mut_ptr_inline() // Copy from stack to heap
};
// Fresh allocation or realloc
let new_ptr = if len == 0 || !was_on_heap {
unsafe { alloc(new_layout) } // First spill: copy from inline
} else {
unsafe { realloc(ptr as *mut u8, old_layout, new_layout.size()) }
};
*self = Self::new_heap(new_ptr, new_capacity);
Ok(())
}
The worst-case O(N) occurs when:
- First spill: copying
Nelements from stack to new heap allocation - Subsequent growth:
reallocmay copy data if allocator can't extend in-place
The Spill/Unspill Cycle
SmallVec can move data back to inline storage via shrink_to_fit():
// From src/lib.rs:1380-1403 (simplified)
pub fn shrink_to_fit(&mut self) {
if !self.spilled() { return; }
let len = self.len();
if len <= Self::inline_size() {
// Move from heap back to stack
unsafe {
let (ptr, capacity) = self.raw.heap;
copy_nonoverlapping(ptr.as_ptr(),
self.raw.as_mut_ptr_inline(),
len);
self.set_inline();
alloc::alloc::dealloc(ptr.cast().as_ptr(), layout);
}
}
}
This "unspilling" allows batch-processing patterns where buffers temporarily grow then return to efficient inline storage.
Zero-Sized Type Optimization
ZSTs never allocate because size_of::<T>() == 0:
// From src/lib.rs:147-151
const fn is_zst<T>() -> bool {
const { size_of::<T>() == 0 }
}
For ZSTs, TaggedLen ignores the heap flag and stores only length. Since no actual bytes are needed, inline_size() returns usize::MAX - effectively infinite capacity without allocation.
Memory Layout Comparison
Both Vec<T> and SmallVec<T, N> use 24 bytes on 64-bit platforms, but SmallVec packs the inline storage directly into its structure through a union-based design.
Standard Vec Layout
A standard Vec<T> contains three fields stored on the stack:
ptr: *mut T- Pointer to heap allocationlen: usize- Current number of elementscap: usize- Total capacity of the heap allocation
This means even a Vec with 1 element requires a heap allocation (typically 4-8 elements minimum from the allocator).
SmallVec Union-Based Layout
SmallVec<T, N> uses a #[repr(C)] union to overlay inline and heap storage:
// From src/lib.rs:132-136
#[repr(C)]
pub union RawSmallVec<T, const N: usize> {
inline: ManuallyDrop<MaybeUninit<[T; N]>>,
heap: (NonNull<T>, usize),
}
When len <= N, the vector stores elements in the inline union member as a MaybeUninit<[T; N]> on the stack. When spilled to heap, it stores (NonNull<T>, usize) in the heap member pointing to the allocation.
TaggedLen: The Storage Discriminator
The key insight is the TaggedLen type which packs both length AND storage location into a single usize:
// From src/lib.rs:302-303, 320-350
#[repr(transparent)]
struct TaggedLen<T>(usize, PhantomData<T>);
impl<T> TaggedLen<T> {
pub const fn new(len: usize, on_heap: bool) -> Self {
if Self::IS_ZST {
Self(len, PhantomData) // ZSTs never spill
} else {
Self((len << 1) | on_heap as usize, PhantomData)
}
}
pub const fn on_heap(self) -> bool {
(self.0 & 1_usize) == 1
}
pub const fn value(self) -> usize {
self.0 >> 1
}
}
Bit 0 indicates heap vs inline (0 = inline, 1 = heap). The remaining bits store length << 1. For ZSTs, the heap flag is always false since they never allocate.
Capacity Query: Runtime Dispatch
The capacity() method checks the heap flag at runtime:
// From src/lib.rs:1041-1049
pub const fn capacity(&self) -> usize {
if self.len.on_heap() {
// SAFETY: raw.heap is active
unsafe { self.raw.heap.1 }
} else {
Self::inline_size()
}
}
When to Choose Which Inline Size
| Capacity | Use Case | Memory Footprint |
|---|---|---|
SmallVec<T, 0> |
Always heap, minimal overhead | Same as Box<[T]> |
SmallVec<T, 4> |
Small collections, single digits | 24 bytes + up to 4×sizeof(T) |
SmallVec<T, 16> |
Buffers, token accumulators | 24 bytes + up to 16×sizeof(T) |
SmallVec<T, 32> |
AST nodes, function args | 24 bytes + up to 32×sizeof(T) |
SmallVec<T, 64> |
Larger working sets | 24 bytes + up to 64×sizeof(T) |
For zero-sized types (ZSTs), inline_size() returns usize::MAX since they require no actual storage:
// From src/lib.rs:1022-1028
pub const fn inline_size() -> usize {
if Self::IS_ZST {
usize::MAX // ZSTs never spill
} else {
N
}
}
This means SmallVec<(), N> for any N will never allocate - it effectively has infinite inline capacity since all elements are zero-sized.
Feature Flags
| Feature | Description | Status |
|---|---|---|
std |
Implements std::io::Write for SmallVec<u8, _> |
Stable |
serde |
Implements Serialize/Deserialize |
Stable |
bytes |
Implements BufMut from bytes crate |
Stable |
extract_if |
Adds extract_if method (unstable API) |
Unstable |
specialization |
Optimized Copy type handling |
Nightly only |
may_dangle |
Relaxed drop check for references | Nightly only |
malloc_size_of |
Memory profiling support | Stable |
Serde Example
use smallvec::{SmallVec, smallvec};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Data {
items: SmallVec<i32, 4>,
}
let data = Data { items: smallvec![1, 2, 3] };
let json = serde_json::to_string(&data).unwrap();
assert_eq!(json, r#"{"items":[1,2,3]}"#);
Write Trait Example
use smallvec::{SmallVec, smallvec};
use std::io::Write;
let mut buf: SmallVec<u8, 32> = smallvec![];
buf.write_all(b"Hello, ").unwrap();
buf.write_all(b"World!").unwrap();
assert_eq!(&buf, b"Hello, World!");
Advanced Usage
Pattern: SmallVec as a Return Type
use smallvec::SmallVec;
// Return small result sets without allocation
fn find_matches(text: &str, pattern: &str) -> SmallVec<(usize, usize), 4> {
let mut matches = SmallVec::new();
let mut start = 0;
while let Some(pos) = text[start..].find(pattern) {
let match_start = start + pos;
matches.push((match_start, match_start + pattern.len()));
if matches.len() == 4 {
break; // Cap at inline capacity
}
start = match_start + 1;
}
matches
}
Pattern: Recursive Data Structures
use smallvec::SmallVec;
// AST node with usually few children
enum Expr {
Literal(f64),
Variable(String),
Call {
func: String,
args: SmallVec<Expr, 4>, // Most functions have few args
},
}
Pattern: Batch Processing with Unspilling
use smallvec::SmallVec;
fn process_batches() {
let mut buffer = SmallVec::<i32, 16>::new();
for i in 0..1000 {
buffer.push(i);
// Process when buffer fills
if buffer.len() == 16 {
process(&buffer);
buffer.clear();
// After shrink_to_fit, back to inline storage
buffer.shrink_to_fit();
}
}
// Process remainder
if !buffer.is_empty() {
process(&buffer);
}
}
fn process(data: &[i32]) {
// Process the batch
}
Comparison with Vec
flowchart TD
subgraph Decision["When to Choose What?"]
Q1{"Typical size?"}
Q1 -->|"Known small\n≤ 16 items"| S1["SmallVec\nStack storage\nNo alloc overhead"]
Q1 -->|"Variable /\nLarge"| S2["Vec\nSimpler\nAlways on heap"]
Q1 -->|"Always 0\nZST pattern"| S3["SmallVec<_, 0>\nAlways heap\nMinimal overhead"]
Q2{"Performance critical?"}
S2 --> Q2
Q2 -->|"Yes, cache\nsensitive"| S4["SmallVec with\nright-sized N"]
Q2 -->|"No, simpler\nis better"| S5["Vec - less\ncomplexity"]
end
| Aspect | Vec | SmallVec |
|---|---|---|
| Stack size | 24 bytes | Varies (N * sizeof(T) + overhead) |
| Heap allocations | Always | Only when len > N |
| Cache locality | Pointer indirection | Direct (inline) or indirect (heap) |
| API compatibility | Standard | Drop-in replacement |
| Memory overhead | Minimal | Same as Vec on heap |
| Use case | General purpose | Known-small collections |
Safety and Invariants
SmallVec maintains the same safety invariants as Vec:
- Length ≤ Capacity: Always true
- Initialized elements: Elements at
0..lenare always valid - Proper alignment: All allocations respect type alignment
- Drop safety: All elements are dropped on destruction
Additional SmallVec-specific invariants:
- TaggedLen encoding: The heap flag correctly indicates storage location
- Union safety: Only the active union variant is accessed
- ZST handling: Zero-sized types never allocate (infinite capacity)
License
This project is licensed under the MIT or Apache-2.0 license, at your option.