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 count

The 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 capacity

The 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 time

Macro 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 elements

Key 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.