Loading page…
Rust walkthroughs
Loading page…
The smallvec crate provides a SmallVec type that stores elements inline (on the stack) up to a certain capacity, only spilling to the heap when that capacity is exceeded. This optimization eliminates heap allocations for small collections, improving performance and reducing memory fragmentation. SmallVec is particularly useful when you know most of your collections will be small, but need to handle larger cases gracefully. It's used extensively in performance-critical code like compilers, game engines, and web browsers.
Key concepts:
# Cargo.toml
[dependencies]
smallvec = "1.13"use smallvec::SmallVec;
fn main() {
// Store up to 4 integers inline
let mut vec: SmallVec<[i32; 4]> = SmallVec::new();
// These are stored on the stack (no heap allocation)
vec.push(1);
vec.push(2);
vec.push(3);
vec.push(4);
println!("Inline: {:?}", vec);
println!("On stack: {}", !vec.spilled());
// This will spill to the heap
vec.push(5);
println!("After spill: {:?}", vec);
println!("Spilled: {}", vec.spilled());
}use smallvec::SmallVec;
fn main() {
// Create with inline capacity for 3 strings
let mut names: SmallVec<[String; 3]> = SmallVec::new();
names.push("Alice".to_string());
names.push("Bob".to_string());
names.push("Carol".to_string());
println!("Names: {:?}", names);
println!("Capacity: {}", names.capacity());
println!("Inline: {}", !names.spilled());
}use smallvec::{smallvec, SmallVec};
fn main() {
// Create with initial values
let nums: SmallVec<[i32; 4]> = smallvec![1, 2, 3];
println!("Numbers: {:?}", nums);
// Create empty
let empty: SmallVec<[i32; 4]> = smallvec![];
println!("Empty: {:?}", empty);
// Create with specific capacity
let with_capacity: SmallVec<[String; 2]> = SmallVec::with_capacity(5);
println!("Capacity: {}", with_capacity.capacity());
}use smallvec::SmallVec;
fn main() {
let mut vec: SmallVec<[i32; 3]> = SmallVec::new();
println!("=== Adding elements ===");
for i in 1..=5 {
vec.push(i);
println!(
"After push({}): len={}, capacity={}, spilled={}",
i, vec.len(), vec.capacity(), vec.spilled()
);
}
// Check if inline
if vec.spilled() {
println!("Data is on the heap");
} else {
println!("Data is on the stack");
}
}use smallvec::SmallVec;
fn main() {
// From Vec to SmallVec
let regular_vec = vec![1, 2, 3, 4, 5];
let small: SmallVec<[i32; 3]> = SmallVec::from_vec(regular_vec);
println!("From Vec: {:?}", small);
println!("Spilled: {}", small.spilled());
// From slice
let slice = &[10, 20, 30][..];
let from_slice: SmallVec<[i32; 4]> = SmallVec::from_slice(slice);
println!("From slice: {:?}", from_slice);
// From SmallVec to Vec
let small_vec: SmallVec<[i32; 2]> = smallvec![1, 2, 3, 4];
let regular: Vec<i32> = small_vec.into_vec();
println!("Into Vec: {:?}", regular);
}use smallvec::{smallvec, SmallVec};
fn main() {
let vec: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
// Iterate by value
let sum: i32 = vec.clone().into_iter().sum();
println!("Sum: {}", sum);
// Drain elements
let mut vec2: SmallVec<[String; 2]> = smallvec!["a".to_string(), "b".to_string(), "c".to_string()];
let drained: Vec<String> = vec2.drain(..).collect();
println!("Drained: {:?}", drained);
println!("Empty: {:?}", vec2);
// Drain a range
let mut vec3: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
let middle: Vec<i32> = vec3.drain(1..4).collect();
println!("Middle: {:?}", middle);
println!("Remaining: {:?}", vec3);
}use smallvec::{smallvec, SmallVec};
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec![10, 20, 30];
// Index access
println!("First: {}", vec[0]);
println!("Second: {}", vec[1]);
// get() returns Option
if let Some(&val) = vec.get(2) {
println!("Third: {}", val);
}
// Mutable access
vec[0] = 100;
println!("After mutation: {:?}", vec);
// first() and last()
println!("First: {:?}", vec.first());
println!("Last: {:?}", vec.last());
// Iteration
for (i, &val) in vec.iter().enumerate() {
println!("Index {}: {}", i, val);
}
}use smallvec::{smallvec, SmallVec};
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
// pop() removes last
let last = vec.pop();
println!("Popped: {:?}", last);
println!("After pop: {:?}", vec);
// remove() at index
vec.remove(1);
println!("After remove(1): {:?}", vec);
// swap_remove() - O(1) but changes order
vec.swap_remove(0);
println!("After swap_remove(0): {:?}", vec);
// truncate
let mut vec2: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
vec2.truncate(3);
println!("After truncate(3): {:?}", vec2);
// clear
vec2.clear();
println!("After clear: {:?}", vec2);
}use smallvec::{smallvec, SmallVec};
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec![1, 3, 5];
// Insert at index
vec.insert(1, 2);
println!("After insert(1, 2): {:?}", vec);
vec.insert(3, 4);
println!("After insert(3, 4): {:?}", vec);
// Extend from another iterator
vec.extend([6, 7, 8]);
println!("After extend: {:?}", vec);
// Extend from slice
let more = &[9, 10];
vec.extend_from_slice(more);
println!("After extend_from_slice: {:?}", vec);
}use smallvec::{smallvec, SmallVec};
fn main() {
let mut vec: SmallVec<[i32; 4]> = smallvec![1, 2];
// Reserve additional capacity
vec.reserve(10);
println!("Capacity after reserve: {}", vec.capacity());
// Resize (fill with default)
vec.resize(5, 0);
println!("After resize(5, 0): {:?}", vec);
// Resize with closure
vec.resize_with(8, Default::default);
println!("After resize_with(8, Default::default): {:?}", vec);
// Shrink to fit
let mut vec2: SmallVec<[i32; 2]> = smallvec![1, 2, 3, 4, 5];
println!("Before shrink: capacity={}", vec2.capacity());
vec2.shrink_to_fit();
println!("After shrink: capacity={}", vec2.capacity());
}use smallvec::{smallvec, SmallVec};
fn main() {
let vec: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
// As slice
let slice: &[i32] = &vec;
println!("As slice: {:?}", slice);
// As mutable slice
let mut vec2: SmallVec<[i32; 4]> = smallvec![1, 2, 3];
{
let slice_mut: &mut [i32] = &mut vec2;
slice_mut[0] = 100;
}
println!("After mutation via slice: {:?}", vec2);
// Split at
let (left, right) = vec.split_at(2);
println!("Left: {:?}", left);
println!("Right: {:?}", right);
}use smallvec::{smallvec, SmallVec};
// Accept SmallVec parameter
fn process_numbers(nums: &SmallVec<[i32; 8]>) -> i32 {
nums.iter().sum()
}
// Return SmallVec
fn get_multiples(n: i32, count: usize) -> SmallVec<[i32; 8]> {
(1..=count as i32).map(|i| n * i).collect()
}
// Generic over array size
fn process_any<const N: usize>(vec: &SmallVec<[i32; N]>) -> i32 {
vec.len() as i32
}
fn main() {
let nums: SmallVec<[i32; 8]> = smallvec![1, 2, 3, 4, 5];
let sum = process_numbers(&nums);
println!("Sum: {}", sum);
let multiples = get_multiples(3, 5);
println!("Multiples of 3: {:?}", multiples);
let vec: SmallVec<[i32; 4]> = smallvec![1, 2, 3];
let len = process_any(&vec);
println!("Length: {}", len);
}use smallvec::{smallvec, SmallVec};
use std::mem::size_of;
fn main() {
// Size comparison
println!("Size of Vec<i32>: {} bytes", size_of::<Vec<i32>>());
println!("Size of SmallVec<[i32; 4]>: {} bytes", size_of::<SmallVec<[i32; 4]>>());
// Create both types
let mut regular: Vec<i32> = Vec::new();
let mut small: SmallVec<[i32; 4]> = SmallVec::new();
// Add same elements
for i in 1..=6 {
regular.push(i);
small.push(i);
}
println!("\nVec contents: {:?}", regular);
println!("SmallVec contents: {:?}", small);
println!("\nVec capacity: {}", regular.capacity());
println!("SmallVec capacity: {}", small.capacity());
println!("SmallVec spilled: {}", small.spilled());
}use smallvec::{smallvec, SmallVec};
use std::time::Instant;
fn bench_vec(n: usize) -> u64 {
let start = Instant::now();
for _ in 0..n {
let mut v = Vec::new();
v.push(1i32);
v.push(2);
v.push(3);
std::hint::black_box(v);
}
start.elapsed().as_nanos() as u64
}
fn bench_smallvec(n: usize) -> u64 {
let start = Instant::now();
for _ in 0..n {
let mut v: SmallVec<[i32; 4]> = SmallVec::new();
v.push(1);
v.push(2);
v.push(3);
std::hint::black_box(v);
}
start.elapsed().as_nanos() as u64
}
fn main() {
let iterations = 1_000_000;
let vec_time = bench_vec(iterations);
let small_time = bench_smallvec(iterations);
println!("Vec time: {} ns", vec_time);
println!("SmallVec time: {} ns", small_time);
println!("Speedup: {:.2}x", vec_time as f64 / small_time as f64);
}use smallvec::{smallvec, SmallVec};
type Row = SmallVec<[i32; 4]>;
type Matrix = SmallVec<[Row; 4]>;
fn main() {
let mut matrix: Matrix = SmallVec::new();
// Add rows (each with inline storage)
matrix.push(smallvec![1, 2, 3]);
matrix.push(smallvec![4, 5, 6]);
matrix.push(smallvec![7, 8, 9]);
println!("Matrix:");
for row in &matrix {
println!(" {:?}", row);
}
// Access element
let val = matrix[1][2];
println!("\nmatrix[1][2] = {}", val);
}use smallvec::{smallvec, SmallVec};
#[derive(Debug, Clone)]
enum Token {
Number(i32),
Operator(char),
Identifier(String),
}
fn tokenize(input: &str) -> SmallVec<[Token; 8]> {
let mut tokens = SmallVec::new();
for word in input.split_whitespace() {
if let Ok(n) = word.parse::<i32>() {
tokens.push(Token::Number(n));
} else if ["+", "-", "*", "/"].contains(&word) {
tokens.push(Token::Operator(word.chars().next().unwrap()));
} else {
tokens.push(Token::Identifier(word.to_string()));
}
}
tokens
}
fn main() {
let input = "let x = 10 + 20 * y";
let tokens = tokenize(input);
println!("Tokens: {:?}", tokens);
println!("Inline: {}", !tokens.spilled());
}use smallvec::{smallvec, SmallVec};
fn split_path(path: &str) -> SmallVec<[&str; 8]> {
path.split('/')
.filter(|s| !s.is_empty())
.collect()
}
fn join_path(segments: &SmallVec<[&str; 8]>) -> String {
segments.join("/")
}
fn main() {
let path = "/usr/local/bin/rustc";
let segments = split_path(path);
println!("Segments: {:?}", segments);
println!("Count: {}", segments.len());
println!("Inline: {}", !segments.spilled());
let rejoined = join_path(&segments);
println!("Rejoined: {}", rejoined);
}use smallvec::SmallVec;
fn process_text(text: &str) -> SmallVec<[char; 32]> {
// Most words are < 32 chars, so inline storage
text.chars().collect()
}
fn reverse_chars(chars: &mut SmallVec<[char; 32]>) {
chars.reverse();
}
fn main() {
let text = "Hello, World!";
let mut chars = process_text(text);
println!("Original: {:?}", chars.iter().collect::<String>());
println!("Inline: {}", !chars.spilled());
reverse_chars(&mut chars);
println!("Reversed: {:?}", chars.iter().collect::<String>());
}use smallvec::{smallvec, SmallVec};
#[derive(Debug)]
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Scroll(i32),
}
struct EventHandler {
// Most frames have < 16 events
pending: SmallVec<[Event; 16]>,
}
impl EventHandler {
fn new() -> Self {
Self {
pending: SmallVec::new(),
}
}
fn push(&mut self, event: Event) {
self.pending.push(event);
}
fn process_all(&mut self) {
for event in self.pending.drain(..) {
match event {
Event::Click { x, y } => println!("Click at ({}, {})", x, y),
Event::KeyPress(c) => println!("Key: {}", c),
Event::Scroll(delta) => println!("Scroll: {}", delta),
}
}
}
}
fn main() {
let mut handler = EventHandler::new();
// Add events
handler.push(Event::Click { x: 100, y: 200 });
handler.push(Event::KeyPress('a'));
handler.push(Event::Scroll(5));
println!("Pending: {}", handler.pending.len());
println!("Inline: {}", !handler.pending.spilled());
handler.process_all();
}use smallvec::{smallvec, SmallVec};
use std::mem::size_of;
fn main() {
// Trade-off: larger inline capacity = more stack space
// Too small: frequent heap allocation
let tiny: SmallVec<[i32; 1]> = smallvec![1, 2]; // Spilled!
println!("Tiny spilled: {}", tiny.spilled());
// Good size: covers common case
let good: SmallVec<[i32; 4]> = smallvec![1, 2, 3]; // Inline!
println!("Good spilled: {}", good.spilled());
// Too large: wastes stack space
println!("Size of SmallVec<[i32; 4]>: {} bytes", size_of::<SmallVec<[i32; 4]>>());
println!("Size of SmallVec<[i32; 64]>: {} bytes", size_of::<SmallVec<[i32; 64]>>());
// Guidelines:
// 1. Analyze your data distribution
// 2. Choose capacity covering 80-90% of cases
// 3. Consider element size (larger elements = smaller capacity)
}use smallvec::{smallvec, SmallVec};
// Convert to Vec for APIs that require it
fn api_requires_vec(items: Vec<i32>) -> i32 {
items.into_iter().sum()
}
// Accept slice for flexibility
fn api_accepts_slice(items: &[i32]) -> i32 {
items.iter().sum()
}
fn main() {
let small: SmallVec<[i32; 4]> = smallvec![1, 2, 3, 4, 5];
// Use as slice (no allocation)
let sum1 = api_accepts_slice(&small);
println!("Sum via slice: {}", sum1);
// Convert to Vec when needed
let sum2 = api_requires_vec(small.into_vec());
println!("Sum via Vec: {}", sum2);
}use smallvec::{smallvec, SmallVec};
use std::mem::{size_of, align_of};
fn main() {
println!("=== Memory Layout ===\n");
println!("Vec<i32>:");
println!(" Size: {} bytes", size_of::<Vec<i32>>());
println!(" Align: {} bytes", align_of::<Vec<i32>>());
println!("\nSmallVec<[i32; 0]> (heap only):" );
println!(" Size: {} bytes", size_of::<SmallVec<[i32; 0]>>());
println!("\nSmallVec<[i32; 4]>:" );
println!(" Size: {} bytes", size_of::<SmallVec<[i32; 4]>>());
println!("\nSmallVec<[i32; 8]>:" );
println!(" Size: {} bytes", size_of::<SmallVec<[i32; 8]>>());
println!("\nSmallVec<[String; 2]>:" );
println!(" Size: {} bytes", size_of::<SmallVec<[String; 2]>>());
}SmallVec<[T; N]> stores up to N elements inline (on stack)spilled() returns true when data is on the heapsmallvec![] macro creates SmallVecs with initial valuesinto_vec() to convert to regular Vec when neededfrom_vec() creates SmallVec from Vec[T] allows slice operations