How does smallvec::SmallVec::into_vec differ from into_inner for consuming the vector?

into_vec always converts a SmallVec into a heap-allocated Vec<T>, potentially copying data from inline storage to the heap, while into_inner attempts to extract the inline array directly and fails if the data has spilled to heap allocation. into_vec is the unconditional conversion path—guaranteed to succeed but may allocate and copy. into_inner is the zero-cost extraction path—succeeds only when the data fits within the inline capacity, avoiding any heap operations. Choosing between them depends on whether you prioritize guaranteed success (into_vec) or maximum performance when possible (into_inner).

The Two Consumption Paths

use smallvec::SmallVec;
 
fn two_paths() {
    // SmallVec with inline capacity of 4
    let small: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    
    // into_vec: Always returns Vec, may allocate
    let vec = small.into_vec();
    assert_eq!(vec, vec![1, 2, 3]);  // Vec<i32> on heap
    
    // into_inner: Returns inline array only if not spilled
    let small2: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let result = small2.into_inner();
    // Succeeds because data fits inline
    assert_eq!(result.unwrap(), [1, 2, 3, 0]);  // Array of size 4
}

into_vec guarantees a Vec; into_inner tries for zero-cost extraction.

into_vec: Unconditional Conversion

use smallvec::SmallVec;
 
fn into_vec_behavior() {
    // Case 1: Data fits inline (stack)
    let small: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let vec = small.into_vec();
    // Copies data from stack to heap
    // Allocates Vec on heap
    assert_eq!(vec, vec![1, 2, 3]);
    
    // Case 2: Data spilled to heap
    let large: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    let vec = large.into_vec();
    // Data already on heap, reuses allocation
    // No copy needed (or minimal copy)
    assert_eq!(vec, vec![1, 2, 3, 4, 5]);
    
    // into_vec always succeeds:
    // Returns Vec<T> regardless of where data was stored
    // Signature: fn into_vec(self) -> Vec<T>
}

into_vec handles both inline and spilled data uniformly.

into_inner: Conditional Extraction

use smallvec::SmallVec;
 
fn into_inner_behavior() {
    // Case 1: Data fits inline (succeeds)
    let small: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let result = small.into_inner();
    assert!(result.is_ok());
    let array = result.unwrap();
    assert_eq!(array, [1, 2, 3, 0]);  // Array of capacity 4
    
    // Case 2: Data spilled to heap (fails)
    let large: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    let result = large.into_inner();
    assert!(result.is_err());
    // Returns Err containing the original SmallVec
    // Cannot extract inline array because data is on heap
    
    // Signature: fn into_inner(self) -> Result<A, Self>
    // where A is the array type (e.g., [i32; 4])
}

into_inner succeeds only when data remained inline.

Memory Layout Implications

use smallvec::SmallVec;
 
fn memory_layout() {
    // SmallVec<[T; N]> stores:
    // - Inline: Array of N elements on stack
    // - Spilled: Vec on heap (for >N elements)
    
    let mut small: SmallVec<[i32; 4]> = SmallVec::new();
    
    // Push elements within capacity
    small.push(1);  // Inline
    small.push(2);  // Inline
    small.push(3);  // Inline
    
    // into_inner succeeds: data is inline
    let array = small.into_inner().unwrap();
    // No heap allocation occurred
    // Array contains [1, 2, 3, 0]
    
    // Now with spilling
    let mut large: SmallVec<[i32; 2]> = SmallVec::new();
    large.push(1);  // Inline
    large.push(2);  // Inline (at capacity)
    large.push(3);  // Spills to heap!
    
    // into_inner fails: data is on heap
    let result = large.into_inner();
    assert!(result.is_err());
    
    // into_vec always works
    let vec = result.unwrap_err().into_vec();
    assert_eq!(vec, vec![1, 2, 3]);
}

Spilling occurs when elements exceed inline capacity.

Allocation Behavior Comparison

use smallvec::SmallVec;
 
fn allocation_behavior() {
    // into_vec allocation behavior:
    //
    // If data is inline (on stack):
    //   1. Allocates heap memory for Vec
    //   2. Copies data from stack to heap
    //   3. Returns Vec pointing to heap
    //
    // If data is spilled (on heap):
    //   1. Reuses existing heap allocation
    //   2. Returns Vec wrapping same allocation
    //   3. Minimal overhead
    
    // into_inner allocation behavior:
    //
    // If data is inline:
    //   1. Returns Ok(array)
    //   2. Zero allocation
    //   3. Zero copy
    //
    // If data is spilled:
    //   1. Returns Err(SmallVec)
    //   2. Zero allocation
    //   3. Returns ownership unchanged
    
    // Example: When to use which
    
    // Use into_inner when you expect inline storage
    // and want zero-cost extraction
    
    // Use into_vec when you need Vec regardless
    // of where data is stored
}

into_inner avoids allocation when successful; into_vec may allocate.

Return Types

use smallvec::SmallVec;
 
fn return_types() {
    let small: SmallVec<[String; 2]> = SmallVec::from_slice(&[
        "a".to_string(),
        "b".to_string(),
    ]);
    
    // into_vec returns Vec<T>
    let vec: Vec<String> = small.into_vec();
    // Always Vec<String>, regardless of storage
    
    let small2: SmallVec<[String; 2]> = SmallVec::from_slice(&[
        "a".to_string(),
        "b".to_string(),
    ]);
    
    // into_inner returns Result<A, Self>
    // where A is the array type
    let result: Result<[String; 2], SmallVec<[String; 2]>> = small2.into_inner();
    
    // Success case: Ok([String; N])
    // Failure case: Err(SmallVec)
    
    // The array type includes the capacity:
    // SmallVec<[T; N]> -> into_inner returns Result<[T; N], Self>
}

into_vec returns Vec<T>; into_inner returns Result<[T; N], Self>.

Handling into_inner Failure

use smallvec::SmallVec;
 
fn handling_failure() {
    let maybe_inline: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    
    // into_inner might fail - handle both cases
    match maybe_inline.into_inner() {
        Ok(array) => {
            // Fast path: data was inline
            println!("Inline: {:?}", array);
        }
        Err(smallvec) => {
            // Data was on heap
            // Can still convert to Vec if needed
            let vec = smallvec.into_vec();
            println!("Spilled: {:?}", vec);
        }
    }
    
    // Common pattern: fallback to into_vec
    let small: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    
    let vec = small.into_inner()
        .map(|arr| arr.to_vec())  // Convert array to Vec
        .unwrap_or_else(|s| s.into_vec());  // Or use into_vec on error
    
    // Or simpler: just use into_vec if you need Vec anyway
}

When into_inner fails, you can fall back to into_vec.

Performance-Sensitive Code

use smallvec::SmallVec;
 
fn performance_sensitive() {
    // Scenario: Hot path where allocation matters
    
    fn process_smallvec(data: SmallVec<[u8; 64]>) -> Vec<u8> {
        // If we expect most data to fit inline:
        // Try into_inner first for zero-cost path
        match data.into_inner() {
            Ok(array) => {
                // Fast path: inline, no allocation
                // Convert only the used portion
                let len = array.iter().position(|&b| b == 0).unwrap_or(64);
                array[..len].to_vec()
            }
            Err(smallvec) => {
                // Slow path: spilled, convert to Vec
                smallvec.into_vec()
            }
        }
    }
    
    // If we always need Vec:
    fn process_to_vec(data: SmallVec<[u8; 64]>) -> Vec<u8> {
        // into_vec is simpler, same result
        data.into_vec()
    }
    
    // The first approach only wins if most data is inline
    // and you want to avoid allocation in that case
}

Use into_inner when you want to optimize the inline case.

Working with the Inline Array

use smallvec::SmallVec;
 
fn working_with_array() {
    let small: SmallVec<[i32; 8]> = SmallVec::from_slice(&[1, 2, 3]);
    
    // into_inner gives the full capacity array
    // Including uninitialized/zeroed elements beyond len
    let array = small.into_inner().unwrap();
    
    // array is [i32; 8] with values:
    // [1, 2, 3, 0, 0, 0, 0, 0]
    // (assuming Default::default() is 0)
    
    // The original SmallVec tracked length 3
    // But the array has capacity 8
    // Be careful to track length separately!
    
    // Common pattern: store length separately
    let small2: SmallVec<[i32; 8]> = SmallVec::from_slice(&[1, 2, 3]);
    let len = small2.len();
    let array = small2.into_inner().unwrap();
    
    // Now use only array[..len]
    for &val in &array[..len] {
        println!("{}", val);
    }
    
    // This is one reason into_vec is often simpler:
    // Vec tracks its own length
}

into_inner returns the full capacity array; track length separately.

Capacity and Spilling

use smallvec::SmallVec;
 
fn capacity_and_spilling() {
    // SmallVec<[T; N]> has inline capacity N
    // Data spills to heap when len > N
    
    let mut vec: SmallVec<[i32; 3]> = SmallVec::new();
    
    println!("Inline capacity: {}", vec.inline_capacity());  // 3
    println!("Current capacity: {}", vec.capacity());  // 3
    
    vec.push(1);
    vec.push(2);
    vec.push(3);
    println!("Len: {}, still inline", vec.len());
    
    vec.push(4);  // Spills!
    println!("Len: {}, now on heap", vec.len());
    println!("Capacity: {} (heap allocated)", vec.capacity());
    
    // At this point:
    // into_inner would fail (data on heap)
    // into_vec would succeed (heap -> heap)
    
    // Creating new SmallVec that fits:
    let fits: SmallVec<[i32; 10]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    assert!(fits.into_inner().is_ok());  // Fits inline
    
    let overflows: SmallVec<[i32; 2]> = SmallVec::from_slice(&[1, 2, 3]);
    assert!(overflows.into_inner().is_err());  // Spilled
}

Spilling depends on the inline capacity and actual element count.

Converting Back to SmallVec

use smallvec::SmallVec;
 
fn back_to_smallvec() {
    // From Vec back to SmallVec
    let vec = vec![1, 2, 3, 4, 5];
    let small: SmallVec<[i32; 10]> = SmallVec::from_vec(vec);
    
    // Data fits inline, into_inner works
    assert!(small.clone().into_inner().is_ok());
    
    // From Vec that overflows
    let large_vec = vec![1, 2, 3, 4, 5];
    let small2: SmallVec<[i32; 2]> = SmallVec::from_vec(large_vec);
    
    // Data spilled, into_inner fails
    assert!(small2.clone().into_inner().is_err());
    
    // From array to SmallVec
    let arr = [1, 2, 3, 4];
    let small3: SmallVec<[i32; 4]> = SmallVec::from_buf(arr);
    
    // into_inner returns the array
    assert_eq!(small3.into_inner().unwrap(), [1, 2, 3, 4]);
}

Conversion paths between SmallVec, Vec, and arrays.

Use Case: Zero-Allocation Processing

use smallvec::SmallVec;
 
fn zero_allocation_example() {
    // Scenario: Process small buffers without heap allocation
    
    fn process_buffer(buf: SmallVec<[u8; 64]>) -> u32 {
        // Try inline path first
        match buf.into_inner() {
            Ok(array) => {
                // Zero-allocation path
                // Process directly from stack
                let len = 64 - array.iter().rev().position(|&b| b != 0).unwrap_or(0);
                array[..len].iter().map(|&b| b as u32).sum()
            }
            Err(smallvec) => {
                // Heap allocation already happened
                // Use into_vec or process inline
                smallvec.iter().map(|&b| b as u32).sum()
            }
        }
    }
    
    // Actually, simpler pattern:
    fn process_simple(buf: SmallVec<[u8; 64]>) -> u32 {
        // If most cases fit inline, into_inner + into_vec
        // is overhead compared to just iterating
        
        // SmallVec derefs to slice, no conversion needed!
        buf.iter().map(|&b| b as u32).sum()
    }
    
    // into_inner is useful when you need ownership of the array
    // not just for avoiding allocation while iterating
}

Often, just iterating over SmallVec avoids needing into_inner.

Use Case: API Boundary

use smallvec::SmallVec;
 
fn api_boundary() {
    // Scenario: Function expects Vec, but you have SmallVec
    
    fn expects_vec(data: Vec<i32>) -> i32 {
        data.into_iter().sum()
    }
    
    let small: SmallVec<[i32; 8]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    
    // into_vec converts to expected type
    let result = expects_vec(small.into_vec());
    
    // If the function could take SmallVec:
    fn accepts_smallvec(data: SmallVec<[i32; 8]>) -> i32 {
        data.into_iter().sum()
    }
    
    let small2: SmallVec<[i32; 8]> = SmallVec::from_slice(&[1, 2, 3, 4, 5]);
    
    // into_inner might extract array for functions expecting arrays
    fn expects_array(data: [i32; 8]) -> i32 {
        data.into_iter().sum()
    }
    
    if let Ok(array) = small2.into_inner() {
        expects_array(array);
    }
}

Use into_vec when interfacing with Vec-based APIs.

Real Example: Compiler Internals

use smallvec::SmallVec;
 
fn compiler_example() {
    // SmallVec is commonly used in compilers
    // where most lists are small but some are large
    
    // Example: List of local variables in a function
    // Most functions have < 8 locals, some have many
    
    type VarList = SmallVec<[u32; 8]>;  // Inline capacity 8
    
    fn process_vars(vars: VarList) -> Vec<String> {
        // Most cases: vars fit inline (8 or fewer)
        // into_inner would succeed
        
        // Some cases: vars spilled to heap (9+)
        // into_inner would fail
        
        // For this use case, just convert to Vec at the end
        vars.into_iter()
            .map(|v| format!("var_{}", v))
            .collect()
    }
    
    // Example: Token buffer during lexing
    type TokenBuf = SmallVec<[Token; 16]>;
    
    #[derive(Clone)]
    struct Token {
        kind: TokenKind,
        span: Span,
    }
    
    #[derive(Clone)]
    struct Span {
        lo: u32,
        hi: u32,
    }
    
    #[derive(Clone)]
    enum TokenKind {
        Ident,
        Literal,
        Operator,
    }
    
    // Tokens accumulate during lexing
    // Most expressions have < 16 tokens
    // into_inner useful for zero-copy extraction
}

Compilers use SmallVec for small-but-sometimes-large collections.

Comparison Summary

use smallvec::SmallVec;
 
fn comparison_summary() {
    // into_vec:
    // - Always succeeds
    // - Returns Vec<T>
    // - May allocate if inline
    // - May copy if inline
    // - Reuses heap if spilled
    // - Simpler to use
    // - Good for Vec-based APIs
    
    // into_inner:
    // - Succeeds only if inline
    // - Returns Result<[T; N], Self>
    // - Never allocates
    // - Never copies (success case)
    // - Returns full capacity array
    // - Requires handling failure
    // - Good for performance optimization
    
    // When to use into_vec:
    // - Need guaranteed Vec
    // - Interfacing with Vec APIs
    // - Simplicity preferred
    // - Don't care about allocation
    
    // When to use into_inner:
    // - Optimizing inline case
    // - Need the array specifically
    // - Performance-critical path
    // - Most data fits inline
    
    let small: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    
    // Simple: always works
    let vec = small.into_vec();
    
    let small2: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    
    // Zero-copy, but may fail
    match small2.into_inner() {
        Ok(array) => println!("Array: {:?}", array),
        Err(s) => println!("Spilled, convert: {:?}", s.into_vec()),
    }
}

Choose based on your success requirements and performance needs.

Synthesis

Quick reference:

use smallvec::SmallVec;
 
fn quick_reference() {
    let inline: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let spilled: SmallVec<[i32; 2]> = SmallVec::from_slice(&[1, 2, 3]);
    
    // into_vec: Always returns Vec
    let vec1: Vec<i32> = inline.into_vec();
    let vec2: Vec<i32> = spilled.into_vec();
    // Both work, vec1 allocated (inline -> heap)
    // vec2 reused heap (already on heap)
    
    // into_inner: Returns Result<[T; N], Self>
    let inlined: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let result1 = inlined.into_inner();
    assert!(result1.is_ok());  // Data was inline
    // Returns Ok([i32; 4])
    
    let spilled2: SmallVec<[i32; 2]> = SmallVec::from_slice(&[1, 2, 3]);
    let result2 = spilled2.into_inner();
    assert!(result2.is_err());  // Data was on heap
    // Returns Err(SmallVec)
    
    // Pattern: Try zero-copy first, fallback to into_vec
    let data: SmallVec<[i32; 4]> = SmallVec::from_slice(&[1, 2, 3]);
    let owned = data.into_inner()
        .map(|arr| arr.to_vec())
        .unwrap_or_else(|s| s.into_vec());
    
    // Key insight:
    // - into_vec: Simple, guaranteed, may allocate
    // - into_inner: Zero-copy if inline, fails if spilled
    // - Use into_vec for simplicity, into_inner for performance
}

Key insight: into_vec and into_inner represent two different ownership transfer strategies for SmallVec. into_vec unconditionally converts to Vec<T>, handling both inline (stack) and spilled (heap) storage transparently—it may allocate and copy if data was inline, or reuse existing heap allocation if spilled. into_inner attempts zero-cost extraction of the inline array, returning Ok([T; N]) when data fits inline and Err(Self) when spilled. Use into_vec when you need guaranteed conversion to Vec regardless of storage location. Use into_inner when optimizing for the common inline case and want to avoid any allocation or copy on success, falling back to into_vec or other handling when spilling occurred.