July 24, 20255 min

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

m
mayo
Vec<T> vs Box<[T]> memory layout Vec<T> ptr len capacity heap: contiguous elements 3 usizes on the stack (24 bytes on 64-bit) Box<[T]> ptr len heap: fixed-size elements 2 usizes on the stack — no capacity field

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)
Cost of the round trip into_boxed_slice() · O(1) when capacity == len Vec<i32> len 3 · cap 3 Box<[i32]> len 3 · exact fit Vec::from(boxed) · O(n), allocates and copies every element If cap > len, the O(1) claim breaks into_boxed_slice() must shrink first: allocate the exact size, copy, free the old block Convert once, when you stop mutating — round-tripping pays a copy in each direction.

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