How does smallvec::smallvec! macro compute inline capacity from the provided elements at compile time?
The smallvec! macro determines inline capacity by counting the number of elements provided at compile time using a recursive macro pattern, then creates a SmallVec with an array capacity exactly matching that count—or one element larger if the count is zero—to avoid zero-sized array issues. This compile-time capacity computation allows SmallVec to allocate exactly enough inline storage for the provided elements, avoiding heap allocation entirely.
The Basic Macro Behavior
use smallvec::{SmallVec, smallvec};
fn basic_usage() {
// Create a SmallVec with 3 elements
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Capacity is exactly 3 - matches element count
// Create a SmallVec with 5 elements
let v: SmallVec<[i32; 5]> = smallvec![1, 2, 3, 4, 5];
// Capacity is exactly 5
// Create an empty SmallVec
let v: SmallVec<[i32; 1]> = smallvec![];
// Capacity is 1 (minimum - zero-sized arrays not allowed)
}The macro inspects the number of elements and creates a SmallVec with capacity matching that count.
How the Macro Counts Elements
// The smallvec! macro uses a counting technique:
// It recursively processes elements and maintains a count
// Simplified view of what the macro does:
// smallvec![a, b, c]
// -> expands to SmallVec::from_buf_and_len([a, b, c], 3)
// -> Array size is [T; 3]
// smallvec![a]
// -> expands to SmallVec::from_buf_and_len([a], 1)
// -> Array size is [T; 1]
// smallvec![]
// -> expands to SmallVec::new() or similar
// -> Array size is [T; 1] (minimum, can't have zero-sized array)
// The counting is done by pattern matching:
// (@count $($x:expr),*) -> counts elements by recursive expansion
// Each element adds 1 to the countThe macro uses recursive pattern matching to count elements at compile time.
Compile-Time Capacity Determination
use smallvec::{SmallVec, smallvec};
fn capacity_examples() {
// Macro infers capacity from element count
// 0 elements -> capacity 1 (minimum)
let v: SmallVec<[i32; 1]> = smallvec![];
assert_eq!(v.capacity(), 1); // Actually SmallVec sets minimum
// 1 element -> capacity 1
let v: SmallVec<[i32; 1]> = smallvec![10];
assert_eq!(v.capacity(), 1);
// 2 elements -> capacity 2
let v: SmallVec<[i32; 2]> = smallvec![10, 20];
assert_eq!(v.capacity(), 2);
// 3 elements -> capacity 3
let v: SmallVec<[i32; 3]> = smallvec![10, 20, 30];
assert_eq!(v.capacity(), 3);
// Each case creates an array sized exactly for the elements
// No heap allocation occurs for these
}The capacity is determined purely from the number of elements in the macro invocation.
The Type Signature Requirement
use smallvec::{SmallVec, smallvec};
fn type_annotation() {
// Type annotation specifies the inline capacity
// The macro must produce a type that matches
// This works: capacity 3 in type matches 3 elements
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// This fails: capacity 5 in type but only 3 elements
// let v: SmallVec<[i32; 5]> = smallvec![1, 2, 3];
// Error: expected array of 5 elements, found array of 3
// This fails: capacity 2 in type but 3 elements
// let v: SmallVec<[i32; 2]> = smallvec![1, 2, 3];
// Error: expected array of 2 elements, found array of 3
// Type annotation and element count must match exactly
}The type annotation SmallVec<[T; N]> specifies the inline capacity, and the macro must produce a matching array.
Inferred Type with Full Syntax
use smallvec::{SmallVec, smallvec};
fn inferred_types() {
// When type can be inferred, the macro determines capacity
fn takes_vec(v: SmallVec<[i32; 3]>) -> SmallVec<[i32; 3]> {
v
}
// The macro produces SmallVec<[i32; 3]> for 3 elements
let v = takes_vec(smallvec![1, 2, 3]);
// Works because macro creates [i32; 3] array
// Alternative: use smallvec_inline! for explicit inline capacity
// But smallvec! infers from element count
}When the type can be inferred from context, the macro capacity must still match.
Macro Expansion Internals
// What the macro actually expands to:
// For: smallvec![a, b, c]
// The macro roughly expands to:
// Step 1: Count elements (compile-time)
// Count = 3 (by counting patterns)
// Step 2: Create array
// [a, b, c] is constructed as [T; 3]
// Step 3: Create SmallVec
// SmallVec::from_buf([a, b, c])
// or
// SmallVec::from_buf_and_len([a, b, c], 3)
// The key insight:
// - The array type [T; N] is determined at compile time
// - N is computed by counting macro arguments
// - The SmallVec inline capacity matches the array size
// For empty: smallvec![]
// Cannot create [T; 0] (zero-sized arrays have issues)
// So it creates [T; 1] with length 0
// This is why SmallVec needs at least 1 element capacityThe macro constructs an array whose size equals the element count, then wraps it in SmallVec.
Why Zero Capacity Isn't Allowed
use smallvec::{SmallVec, smallvec};
fn zero_capacity() {
// Zero-sized inline arrays are problematic
// This would be nice:
// let v: SmallVec<[i32; 0]> = smallvec![];
// But SmallVec doesn't support zero capacity
// Reasons:
// 1. Zero-sized arrays have special rules in Rust
// 2. Pointer arithmetic on zero-sized arrays is tricky
// 3. The implementation assumes at least 1 capacity
// Instead, empty smallvec! uses capacity 1:
let v: SmallVec<[i32; 1]> = smallvec![];
assert!(v.is_empty());
assert_eq!(v.capacity(), 1); // Minimum capacity
// This wastes a tiny bit of space but avoids complexity
}The minimum inline capacity is 1 because zero-sized arrays cause implementation issues.
Comparing with SmallVec::new
use smallvec::{SmallVec, smallvec};
fn new_vs_macro() {
// SmallVec::new requires explicit capacity
let v: SmallVec<[i32; 8]> = SmallVec::new();
// Capacity is 8, no elements
// smallvec! infers capacity from elements
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Capacity is exactly 3
// SmallVec::from_buf also requires explicit size
let v: SmallVec<[i32; 3]> = SmallVec::from_buf([1, 2, 3]);
// But you must specify the type
// smallvec! combines:
// 1. Array construction from elements
// 2. Capacity inference from count
// 3. SmallVec construction
// All in one convenient syntax
}The macro provides syntax sugar for creating a SmallVec with the right capacity.
The Counting Mechanism
// How smallvec! counts elements (simplified):
// The macro uses a technique called "TT munching" or "token tree counting"
// It recursively matches patterns and accumulates a count
// Simplified pseudocode:
// smallvec![@count $head:expr, $($tail:expr),*] => {
// smallvec![@count + 1 $($tail),*] // recursively add 1
// }
// smallvec![@count $head:expr] => {
// smallvec![@count + 1] // single element
// }
// smallvec![@count] => {
// // count is known, produce SmallVec with that capacity
// }
// For smallvec![a, b, c]:
// 1. Match @count a, b, c -> recurse with count + 1, remaining [b, c]
// 2. Match @count b, c -> recurse with count + 1, remaining [c]
// 3. Match @count c -> recurse with count + 1, remaining []
// 4. Match @count -> count is 3, create [T; 3] array
// The actual implementation uses more sophisticated patterns
// but the principle is the same: count at compile timeMacro counting uses recursive pattern matching to compute the element count at compile time.
Practical Examples with Type Inference
use smallvec::{SmallVec, smallvec};
fn practical_examples() {
// Common pattern: inline capacity for known-size collections
// Point coordinates (always 2 or 3)
let point_2d: SmallVec<[f64; 2]> = smallvec![1.0, 2.0];
let point_3d: SmallVec<[f64; 3]> = smallvec![1.0, 2.0, 3.0];
// RGB color (always 3)
let color: SmallVec<[u8; 3]> = smallvec![255, 128, 0];
// Small number of known elements
let flags: SmallVec<[bool; 4]> = smallvec![true, false, true, false];
// All of these avoid heap allocation
// The capacity is determined at compile time from element count
// When you push more elements:
let mut v: SmallVec<[i32; 2]> = smallvec![1, 2];
v.push(3); // Spills to heap, inline capacity stays 2
assert!(v.len() > v.capacity()); // len=3, inline capacity=2
}When element counts are known at compile time, smallvec! provides exactly the right capacity.
Performance Implications
use smallvec::{SmallVec, smallvec};
fn performance() {
// smallvec! creates SmallVec in one operation
// No reallocation, no capacity calculations at runtime
// Compare to:
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
// Vec may reallocate
// With smallvec!:
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Array [1, 2, 3] constructed directly on stack
// No heap allocation, no reallocation
// The compile-time capacity means:
// 1. Array size is known to compiler
// 2. Stack space is reserved exactly
// 3. Elements are placed directly in inline storage
// 4. No overhead for capacity checking during construction
}Compile-time capacity eliminates runtime overhead for initial allocation.
The smallvec_inline! Variant
use smallvec::{SmallVec, smallvec_inline};
fn inline_variant() {
// smallvec_inline! allows explicit capacity
// Useful when capacity != element count
// Create with capacity 8, 3 elements
let v: SmallVec<[i32; 8]> = smallvec_inline![1, 2, 3; 8];
// Capacity is 8, length is 3
// Compare to smallvec! which would require:
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Capacity is 3, length is 3
// smallvec_inline! syntax: [elements; capacity]
// If capacity matches element count, it's equivalent to smallvec!
}smallvec_inline! provides explicit capacity control when needed.
Empty SmallVec Behavior
use smallvec::{SmallVec, smallvec};
fn empty_behavior() {
// Empty smallvec! creates SmallVec with capacity 1
let v: SmallVec<[i32; 1]> = smallvec![];
// Why capacity 1?
// 1. Zero-sized arrays are special in Rust
// 2. SmallVec uses array [T; N] for inline storage
// 3. [T; 0] exists but creates issues for SmallVec's implementation
// The implementation details:
// - SmallVec stores data in a union of inline array and heap pointer
// - Zero capacity would need different handling
// - Minimum of 1 simplifies the implementation
// Effect: smallvec![] uses 1 element worth of stack space
// even though it's empty
// Type annotation must specify at least capacity 1:
// let v: SmallVec<[i32; 0]> = smallvec![]; // Doesn't compile
}Empty smallvec![] creates a SmallVec with minimum capacity 1.
Const Context Considerations
use smallvec::{SmallVec, smallvec};
fn const_considerations() {
// smallvec! cannot be used in const context
// (SmallVec const construction is limited)
// This doesn't work:
// const V: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// SmallVec::new() isn't const
// Workaround: use const array and convert
const ARR: [i32; 3] = [1, 2, 3];
fn create_smallvec() -> SmallVec<[i32; 3]> {
SmallVec::from_buf(ARR)
}
// The macro itself isn't const because:
// 1. SmallVec construction involves non-const operations
// 2. FromBuf implementation isn't const
// The counting part IS compile-time, but construction is not const
}The macro isn't const-compatible because SmallVec construction isn't const.
Memory Layout
use smallvec::{SmallVec, smallvec};
fn memory_layout() {
// SmallVec<[T; N]> stores inline array + metadata
// For: SmallVec<[i32; 3]>
// Memory on stack:
// - Inline buffer: [i32; 3] = 12 bytes
// - Length: usize = 8 bytes (on 64-bit)
// - Capacity indicator: usually encoded in length/capacity union
// - Total: ~20 bytes on stack
// When elements <= 3: all on stack
// When elements > 3: spills to heap
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// All 3 elements in inline storage
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3, 4, 5];
// Would overflow inline capacity - but wait, this doesn't compile!
// Macro requires type and element count to match
// To create with heap allocation:
let mut v: SmallVec<[i32; 3]> = SmallVec::new();
v.push(1);
v.push(2);
v.push(3);
v.push(4); // Now on heap
// This works because we don't use smallvec! macro
}The macro enforces that initial elements fit within the declared inline capacity.
Compile-Time Enforcement
use smallvec::{SmallVec, smallvec};
fn compile_time_enforcement() {
// The compiler checks capacity at compile time
// This compiles: type matches element count
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// This fails: mismatched capacity
// let v: SmallVec<[i32; 5]> = smallvec![1, 2, 3];
// Error: mismatched types
// expected `SmallVec<[i32; 5]>`
// found `SmallVec<[i32; 3]>`
// The macro produces SmallVec<[T; N]> where N = element count
// Type annotation must match exactly
// This provides compile-time safety:
// - You can't accidentally create mismatched capacity
// - Type system ensures alignment between declaration and elements
}The compiler ensures declared capacity matches the number of elements provided.
Comparison with Vec!
use smallvec::{SmallVec, smallvec};
fn macro_comparison() {
// vec! behavior:
let v: Vec<i32> = vec![1, 2, 3];
// Capacity may be >= 3 (implementation detail)
// Always on heap
// smallvec! behavior:
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Capacity is exactly 3
// On stack until capacity exceeded
// vec! with capacity:
let v: Vec<i32> = Vec::with_capacity(3);
v.push(1); v.push(2); v.push(3);
// Still on heap, capacity 3
// smallvec! equivalent:
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// On stack, capacity 3
// Key difference:
// - vec! creates Vec with unspecified capacity
// - smallvec! creates SmallVec with known capacity
// - smallvec! capacity is compile-time constant
// - vec! capacity is runtime value
}smallvec! provides stack allocation with compile-time capacity, unlike vec!.
Summary Table
fn summary() {
// | Expression | Inline Capacity | Allocation |
// |--------------------------------|-----------------|------------|
// | smallvec![] | 1 | Stack |
// | smallvec![a] | 1 | Stack |
// | smallvec![a, b] | 2 | Stack |
// | smallvec![a, b, c] | 3 | Stack |
// | smallvec_inline![a, b; 5] | 5 | Stack |
// | Type Annotation | Elements Allowed | Compile? |
// |-------------------------------|-------------------|-----------|
// | SmallVec<[T; 1]> | 1 | Yes |
// | SmallVec<[T; 3]> | 3 | Yes |
// | SmallVec<[T; 0]> | None (invalid) | N/A |
// | Macro Step | When It Happens |
// |-------------------------------|-------------------|
// | Count elements | Compile time |
// | Create array [T; N] | Compile time |
// | Create SmallVec | Runtime |
}Synthesis
Quick reference:
use smallvec::{SmallVec, smallvec};
// Macro infers capacity from element count at compile time
let v: SmallVec<[i32; 3]> = smallvec![1, 2, 3];
// Capacity = element count = 3
// Type annotation must match element count
// let v: SmallVec<[i32; 5]> = smallvec![1, 2, 3]; // Compile error!
// Empty uses minimum capacity 1
let v: SmallVec<[i32; 1]> = smallvec![];
// Elements go directly into inline storage
// No heap allocation for initial elementsKey insight: The smallvec! macro computes inline capacity at compile time through recursive token counting—the macro pattern matches each element and accumulates a count, then produces an array type [T; N] where N is exactly the number of elements. This count becomes the SmallVec's inline capacity, which must match the type annotation SmallVec<[T; N]>. The compile-time nature of this computation means the compiler knows exactly how much stack space to allocate, and there's no runtime overhead for determining capacity. The minimum capacity is 1 because zero-sized arrays create implementation complexity for SmallVec's internal union of inline array and heap pointer. The enforcement is strict: if your type annotation says SmallVec<[T; 5]> but you provide 3 elements, compilation fails—the macro produces a SmallVec<[T; 3]> which doesn't match. This compile-time mismatch detection is a feature, ensuring the declared capacity always reflects what the macro provides. For cases where capacity should differ from initial elements, use SmallVec::new() and push elements, or use smallvec_inline! with explicit capacity.
