July 24, 20252 min

What is the difference between Box<[T]> and Vec<T>?

m
mayo

Key Differences

Feature Vec Box<[T]>
Size Mutability Growable/shrinkable (push, pop) Fixed-size (immutable after creation)
Storage Heap-allocated + capacity field Pure heap slice (no extra metadata)
Memory Overhead 3 usizes (ptr, len, capacity) 2 usizes (ptr, len)
Conversion Cost O(1) to Box<[T]> (shrink-to-fit) O(n) to Vec (must reallocate)

When to Use Each

Prefer Vec When:

You need dynamic resizing:

let mut vec = vec![1, 2, 3];
vec.push(4);  // Works

You frequently modify the collection (e.g., appending/removing elements).

Prefer Box<[T]> When:

You want a fixed-size, immutable collection:

let boxed_slice: Box<[i32]> = vec![1, 2, 3].into_boxed_slice();
// boxed_slice.push(4);  // ERROR: No `push` method

Memory efficiency matters (e.g., embedded systems):

  • Saves 1 usize (no unused capacity).

Interfacing with APIs requiring owned slices:

fn process(data: Box<[i32]>) { /* ... */ }

Conversion Between Them

Direction Code Cost
Vec → Box<[T]> vec.into_boxed_slice() O(1)
Box<[T]> → Vec Vec::from(boxed_slice) O(n)

Example:

let vec = vec![1, 2, 3];
let boxed: Box<[i32]> = vec.into_boxed_slice();  // No reallocation
let vec_again = Vec::from(boxed);                // Copies data

Performance Implications

  • Iteration: Identical (both are contiguous heap arrays).
  • Memory: Box<[T]> avoids unused capacity overhead.
  • Flexibility: Vec supports in-place growth; Box<[T]> does not.

Real-World Use Cases

  • Vec: Buffers for dynamic data (e.g., HTTP request bodies).
  • Box<[T]>:
    • Configurations loaded once and never modified.
    • Storing large immutable datasets (e.g., game assets).

Key Takeaways

✅ Use Vec for mutable, growable sequences. ✅ Use Box<[T]> for immutable, memory-efficient storage. ⚡ Convert cheaply from Vec to Box<[T]> when done modifying.

Try This: What happens if you convert a Vec with spare capacity to Box<[T]>?

Answer: into_boxed_slice() shrinks the allocation to exact size (no unused capacity).