Loading page…
Rust walkthroughs
Loading page…
The smallvec crate provides a SmallVec type that stores elements inline (on the stack) for small collections, automatically spilling to the heap when the collection grows beyond its inline capacity. This optimization is valuable when most of your vectors are small but you still need to handle the occasional large collection. It eliminates heap allocations for the common case, improving cache locality and reducing memory fragmentation. SmallVec is particularly useful in performance-critical code, embedded systems, and situations where allocation overhead matters.
Key concepts:
SmallVec<[T; N]> uses array [T; N] as backing storagepush, pop, iter, etc.# Cargo.toml
[dependencies]
smallvec = "1"use smallvec::SmallVec;
fn main() {
// Inline capacity for 4 elements
let mut vec: SmallVec<[i32; 4]> = SmallVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
vec.push(4);
println!("Vec length: {}", vec.len());
}use smallvec::SmallVec;
fn main() {
// Create empty SmallVec with inline capacity for 4 i32s
let mut vec: SmallVec<[i32; 4]> = SmallVec::new();
// Create with initial capacity (still inline-first)
let vec: SmallVec<[i32; 4]> = SmallVec::with_capacity(10);
// Create from a slice
let vec: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
// Create from an array (always inline)
let vec: SmallVec<[i32; 4]> = SmallVec::from([1, 2, 3, 4]);
// Create using smallvec! macro
let vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
// Create from_buf (takes ownership of array)
let vec: SmallVec<[i32; 4]> = SmallVec::from_buf([1, 2, 3, 4]);
println!("Created SmallVecs with various methods");
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[String; 2]> = SmallVec::new();
// Push elements
vec.push("hello".to_string());
vec.push("world".to_string());
println!("After 2 pushes: {:?}", vec);
// Still inline!
println!("Is inline: {}", !vec.spilled());
// Push beyond inline capacity
vec.push("overflow".to_string());
println!("After spill: {:?}", vec);
println!("Is spilled: {}", vec.spilled());
// Pop elements
let last = vec.pop();
println!("Popped: {:?}", last);
// Insert at position
vec.insert(1, "inserted".to_string());
println!("After insert: {:?}", vec);
// Remove at position
let removed = vec.remove(0);
println!("Removed: {:?}", removed);
// Clear
vec.clear();
println!("After clear: {:?}", vec);
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 3]> = SmallVec::new();
println!("Initial:");
println!(" len: {}", vec.len());
println!(" capacity: {}", vec.capacity());
println!(" spilled: {}", vec.spilled());
vec.push(1);
vec.push(2);
vec.push(3);
println!("\nAfter 3 pushes (at capacity):");
println!(" len: {}", vec.len());
println!(" capacity: {}", vec.capacity());
println!(" spilled: {}", vec.spilled());
vec.push(4);
println!("\nAfter 4th push (spilled):");
println!(" len: {}", vec.len());
println!(" capacity: {}", vec.capacity());
println!(" spilled: {}", vec.spilled());
}use smallvec::SmallVec;
// Accept any SmallVec with inline capacity 4
fn process_smallvec(vec: &mut SmallVec<[i32; 4]>) {
for i in vec.iter_mut() {
*i *= 2;
}
}
// Return SmallVec to avoid heap allocation
fn get_coordinates() -> SmallVec<[f64; 3]> {
let mut coords = SmallVec::new();
coords.push(1.0);
coords.push(2.0);
coords.push(3.0);
coords
}
// Generic over smallvec size
fn print_vec<T, const N: usize>(vec: &SmallVec<[T; N]>)
where
T: std::fmt::Debug,
{
println!("Contents: {:?}", vec.as_slice());
}
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
process_smallvec(&mut vec);
println!("After processing: {:?}", vec);
let coords = get_coordinates();
println!("Coordinates: {:?}", coords);
let vec: SmallVec<[String; 2]> = smallvec::smallvec!["a".to_string()];
print_vec(&vec);
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3, 4, 5];
// Immutable iteration
println!("Elements:");
for elem in vec.iter() {
println!(" {}", elem);
}
// Mutable iteration
for elem in vec.iter_mut() {
*elem *= 2;
}
println!("After doubling: {:?}", vec);
// Into iteration (consumes)
let vec: SmallVec<[String; 2]> = smallvec::smallvec!["a".to_string(), "b".to_string()];
for elem in vec.into_iter() {
println!("Owned: {}", elem);
}
}use smallvec::SmallVec;
fn main() {
let vec: SmallVec<[i32; 4]> = smallvec::smallvec![10, 20, 30, 40, 50];
// Index
println!("First: {}", vec[0]);
println!("Third: {}", vec[2]);
// Get slice
let slice: &[i32] = vec.as_slice();
println!("Slice: {:?}", slice);
// Get mutable slice
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
{
let slice = vec.as_mut_slice();
slice[0] = 100;
}
println!("After mutation: {:?}", vec);
// Slice patterns
match vec.as_slice() {
[first, rest @ ..] => println!("First: {}, Rest: {:?}", first, rest),
[] => println!("Empty"),
}
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 4]> = SmallVec::new();
// Reserve capacity
vec.reserve(10);
println!("After reserve(10): capacity = {}", vec.capacity());
// Push elements
for i in 0..15 {
vec.push(i);
}
println!("After 15 pushes: len = {}, capacity = {}", vec.len(), vec.capacity());
// Shrink to fit
vec.shrink_to_fit();
println!("After shrink_to_fit: capacity = {}", vec.capacity());
// Truncate
vec.truncate(5);
println!("After truncate(5): len = {}", vec.len());
// Reserve exact
vec.reserve_exact(10);
println!("After reserve_exact(10): capacity = {}", vec.capacity());
}use smallvec::SmallVec;
fn main() {
// Collect into SmallVec
let vec: SmallVec<[i32; 4]> = (1..=10).collect();
println!("Collected: {:?}", vec);
println!("Spilled: {}", vec.spilled());
// Extend with iterator
let mut vec: SmallVec<[String; 2]> = smallvec::smallvec!["a".to_string()];
vec.extend(["b".to_string(), "c".to_string(), "d".to_string()]);
println!("Extended: {:?}", vec);
// From iterator
let vec: SmallVec<[i32; 3]> = SmallVec::from_iter([1, 2, 3]);
println!("From iter: {:?}", vec);
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3, 4, 5];
// Drain elements from index 1..4
let drained: Vec<i32> = vec.drain(1..4).collect();
println!("Drained: {:?}", drained);
println!("Remaining: {:?}", vec);
// Drain all
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![10, 20, 30];
let all: Vec<i32> = vec.drain(..).collect();
println!("All drained: {:?}", all);
println!("Vec is now empty: {}", vec.is_empty());
// into_vec() converts to Vec (may avoid allocation if spilled)
let vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3, 4, 5];
let regular_vec: Vec<i32> = vec.into_vec();
println!("Regular Vec: {:?}", regular_vec);
}use smallvec::SmallVec;
fn main() {
// into_inner() returns the array if not spilled
let vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
match vec.into_inner() {
Ok(arr) => println!("Got array: {:?}", arr),
Err(vec) => println!("Was spilled, got back SmallVec with {} elements", vec.len()),
}
// Spilled case
let vec: SmallVec<[i32; 2]> = smallvec::smallvec![1, 2, 3, 4, 5];
match vec.into_inner() {
Ok(arr) => println!("Got array: {:?}", arr),
Err(vec) => println!("Was spilled, got back SmallVec with {} elements", vec.len()),
}
}use smallvec::SmallVec;
#[derive(Debug, Clone)]
enum Token {
Identifier(String),
Number(i64),
Operator(char),
}
// Most expressions have few tokens, so SmallVec is perfect
fn tokenize(input: &str) -> SmallVec<[Token; 8]> {
let mut tokens = SmallVec::new();
for word in input.split_whitespace() {
let token = if let Ok(n) = word.parse::<i64>() {
Token::Number(n)
} else if word.len() == 1 && "+-*/".contains(word) {
Token::Operator(word.chars().next().unwrap())
} else {
Token::Identifier(word.to_string())
};
tokens.push(token);
}
tokens
}
fn main() {
let expr = "x + 42 * y";
let tokens = tokenize(expr);
println!("Tokens ({} items, spilled: {}):", tokens.len(), tokens.spilled());
for token in tokens.iter() {
println!(" {:?}", token);
}
}use smallvec::SmallVec;
#[derive(Debug, Clone)]
enum Event {
MouseMove { x: i32, y: i32 },
Click { button: u8 },
KeyPress(char),
}
struct EventQueue {
// Most frames have few events, use SmallVec
events: SmallVec<[Event; 16]>,
}
impl EventQueue {
fn new() -> Self {
Self { events: SmallVec::new() }
}
fn push(&mut self, event: Event) {
self.events.push(event);
}
fn process(&mut self) {
for event in self.events.drain(..) {
match event {
Event::MouseMove { x, y } => println!("Mouse moved to ({}, {})", x, y),
Event::Click { button } => println!("Button {} clicked", button),
Event::KeyPress(c) => println!("Key pressed: {}", c),
}
}
}
}
fn main() {
let mut queue = EventQueue::new();
queue.push(Event::MouseMove { x: 100, y: 200 });
queue.push(Event::Click { button: 1 });
queue.push(Event::KeyPress('a'));
println!("Events: {} (spilled: {})", queue.events.len(), queue.events.spilled());
queue.process();
}use smallvec::SmallVec;
// Most paths have few segments
struct Path {
segments: SmallVec<[String; 4]>,
}
impl Path {
fn new() -> Self {
Self { segments: SmallVec::new() }
}
fn from_str(s: &str) -> Self {
let segments = s.split('/')
.filter(|s| !s.is_empty())
.map(String::from)
.collect();
Self { segments }
}
fn push(&mut self, segment: &str) {
self.segments.push(segment.to_string());
}
fn pop(&mut self) -> Option<String> {
self.segments.pop()
}
fn to_string(&self) -> String {
let mut result = String::from("/");
for (i, segment) in self.segments.iter().enumerate() {
if i > 0 {
result.push('/');
}
result.push_str(segment);
}
result
}
}
fn main() {
let path = Path::from_str("/home/user/documents/file.txt");
println!("Path: {}", path.to_string());
println!("Segments: {} (spilled: {})", path.segments.len(), path.segments.spilled());
}use smallvec::SmallVec;
struct Calculator {
// Most calculations need few values on stack
stack: SmallVec<[f64; 8]>,
}
impl Calculator {
fn new() -> Self {
Self { stack: SmallVec::new() }
}
fn push(&mut self, value: f64) {
self.stack.push(value);
}
fn add(&mut self) -> Result<f64, &'static str> {
if self.stack.len() < 2 {
return Err("Need at least 2 values");
}
let b = self.stack.pop().unwrap();
let a = self.stack.pop().unwrap();
self.stack.push(a + b);
Ok(a + b)
}
fn mul(&mut self) -> Result<f64, &'static str> {
if self.stack.len() < 2 {
return Err("Need at least 2 values");
}
let b = self.stack.pop().unwrap();
let a = self.stack.pop().unwrap();
self.stack.push(a * b);
Ok(a * b)
}
fn result(&self) -> Option<f64> {
self.stack.last().copied()
}
}
fn main() {
let mut calc = Calculator::new();
// Calculate (3 + 4) * 2
calc.push(3.0);
calc.push(4.0);
calc.add().unwrap();
calc.push(2.0);
calc.mul().unwrap();
println!("Result: {}", calc.result().unwrap());
println!("Stack spilled: {}", calc.stack.spilled());
}use smallvec::SmallVec;
use std::time::Instant;
fn benchmark_vec(count: usize) -> std::time::Duration {
let start = Instant::now();
let mut total = 0;
for _ in 0..count {
let mut vec = Vec::new();
vec.push(1);
vec.push(2);
vec.push(3);
total += vec.len();
}
start.elapsed()
}
fn benchmark_smallvec(count: usize) -> std::time::Duration {
let start = Instant::now();
let mut total = 0;
for _ in 0..count {
let mut vec: SmallVec<[i32; 4]> = SmallVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
total += vec.len();
}
start.elapsed()
}
fn main() {
let count = 1_000_000;
let vec_time = benchmark_vec(count);
let smallvec_time = benchmark_smallvec(count);
println!("Vec: {:?}", vec_time);
println!("SmallVec: {:?}", smallvec_time);
println!("Speedup: {:.2}x", vec_time.as_secs_f64() / smallvec_time.as_secs_f64());
}use smallvec::SmallVec;
fn main() {
// Vec: always heap allocates
let regular_vec: Vec<i32> = vec![1, 2, 3];
println!("Vec: {:?}", regular_vec);
// SmallVec: stack-allocated for small collections
let small: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
println!("SmallVec (inline): {:?}", small);
println!("Spilled: {}", small.spilled());
// SmallVec grows beyond inline capacity
let large: SmallVec<[i32; 2]> = smallvec::smallvec![1, 2, 3, 4, 5];
println!("SmallVec (spilled): {:?}", large);
println!("Spilled: {}", large.spilled());
// Convert between
let vec: Vec<i32> = small.into_vec();
println!("Converted to Vec: {:?}", vec);
}use smallvec::SmallVec;
// Generic function accepting SmallVec
fn sum<T, const N: usize>(vec: &SmallVec<[T; N]>) -> T
where
T: std::ops::Add<Output = T> + Default + Copy,
{
vec.iter().copied().fold(T::default(), |acc, x| acc + x)
}
// Return SmallVec from function
fn range_smallvec(start: i32, end: i32) -> SmallVec<[i32; 16]> {
(start..end).collect()
}
fn main() {
let vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3, 4, 5];
println!("Sum: {}", sum(&vec));
let range = range_smallvec(1, 10);
println!("Range: {:?}", range);
}use smallvec::SmallVec;
fn main() {
let original: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3];
// Clone
let cloned = original.clone();
println!("Cloned: {:?}", cloned);
// Clone from slice
let slice = &[10, 20, 30];
let from_slice = SmallVec::<[i32; 4]>::from_slice(slice);
println!("From slice: {:?}", from_slice);
// Note: SmallVec is not Copy even if T is Copy,
// because it may have spilled to heap
}use smallvec::SmallVec;
struct Droppable {
id: i32,
}
impl Drop for Droppable {
fn drop(&mut self) {
println!("Dropping {}", self.id);
}
}
fn main() {
println!("Creating SmallVec...");
{
let mut vec: SmallVec<[Droppable; 2]> = SmallVec::new();
vec.push(Droppable { id: 1 });
vec.push(Droppable { id: 2 });
vec.push(Droppable { id: 3 }); // Causes spill
println!("Going out of scope...");
}
println!("SmallVec dropped.");
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![1, 2, 3, 4, 5, 6, 7, 8];
// Drain specific range
let middle: Vec<i32> = vec.drain(2..5).collect();
println!("Drained middle: {:?}", middle);
println!("Remaining: {:?}", vec);
// Drain from index to end
let mut vec: SmallVec<[i32; 4]> = smallvec::smallvec![10, 20, 30, 40];
let tail: Vec<i32> = vec.drain(2..).collect();
println!("Drained tail: {:?}", tail);
println!("Remaining: {:?}", vec);
}SmallVec<[T; N]> stores N elements inline on the stack.spilled() to check if heap allocation occurred.into_inner() returns the array if not spilled.into_vec() converts to Vec (may avoid allocation)Vec for easy migrationsmallvec! macro for literal construction