Skip to content

Vec::into_boxed_slice

Vec methods · Collections

Level: reference · for working programmers

One line: Consume the vector and give back a Box<[T]> — the buffer, exactly sized.

pub fn into_boxed_slice(self) -> Box<[T], A>

Stable since 1.0.0.

It shrinks first (handing back any spare capacity) and then drops the capacity field — literally: shrink_to_fit is its first statement, so the two make the same request of the allocator. The result is two words instead of three: a Box<[T]> is pointer plus length, where a Vec<T> is pointer, length and capacity.

Which is why it is only free when there is nothing to hand back. At len == capacity there is no work to do: same allocation, one field dropped, no element moved. With spare capacity the allocator is asked to resize the block, and whether the buffer moves — copying every element on the way — is its decision, not something to assert on. Measured here on macOS at rustc 1.98.0, shrinking a Vec<u32> from 100 slots to 3 kept the same address; that is one allocator's answer rather than a promise, which is why the number is in this sentence and not in the answer-keyed example below.

The type is the real payoff. Box<[T]> says this will not grow — useful for a struct field built once and then only read, and one word smaller per value.

A cache entry is the archetype. Cloudflare's 1.1.1.1 resolver replaced the 8 Vec and String fields in each of its DNS cache entries with Box<[T]> and Box<str> — 64 header bytes per entry plus the heap slots the doubling had reserved, over 250 billion entries, more than 15 terabytes ↗ of memory returned.

It is still a slice, so it derefs the same way: first, contains, sort and the rest all work, and it is mutable in place at a fixed length.

The round trip is cheap in both directions: boxed.into_vec() gives a Vec back with capacity equal to the length, and Vec::from(boxed) goes the other way.

Compare shrink_to_fit, which performs the same shrink but keeps the Vec type and its ability to grow again.

Example

vec_into_boxed_slice.rs in full — pasted here by tools/run_examples.py from the file CI compiles and runs.

fn main() {
    // Drops the capacity field: the result is exactly as long as it is.
    let v = vec![1, 2, 3];
    let boxed: Box<[i32]> = v.into_boxed_slice();
    println!("{boxed:?} len {}", boxed.len());

    // Two words instead of three. A Box<[T]> is pointer + length.
    println!("Vec<i32> {} bytes, Box<[i32]> {} bytes",
             size_of::<Vec<i32>>(), size_of::<Box<[i32]>>());

    // It shrinks first, so spare capacity is handed back on the way.
    let mut v: Vec<u8> = Vec::with_capacity(100);
    v.extend_from_slice(&[1, 2, 3]);
    println!("before: len {} cap {}", v.len(), v.capacity());
    let boxed = v.into_boxed_slice();
    println!("after: len {}, and there is no capacity to ask about", boxed.len());

    // It is still a slice, so slice methods work and it derefs the same way.
    let boxed: Box<[i32]> = vec![3, 1, 2].into_boxed_slice();
    println!("first {:?} contains 2: {}", boxed.first(), boxed.contains(&2));

    // Mutable in place, still fixed length.
    let mut boxed: Box<[i32]> = vec![3, 1, 2].into_boxed_slice();
    boxed.sort();
    println!("{boxed:?}");

    // The round trip is free in both directions.
    let back: Vec<i32> = boxed.into_vec();
    println!("into_vec: {back:?} cap {}", back.capacity());
    let again: Box<[i32]> = back.into();
    println!("and back: {again:?}");

    // Why bother: a field that is built once and then only read costs one
    // word less per value, and the type says "this will not grow".
    struct Frozen { data: Box<[u8]> }
    let f = Frozen { data: vec![1, 2, 3].into_boxed_slice() };
    println!("{:?}", f.data);
}

Verified output of vec_into_boxed_slice.rs — regenerated by tools/run_examples.py, never hand-typed.

[1, 2, 3] len 3
Vec<i32> 24 bytes, Box<[i32]> 16 bytes
before: len 3 cap 100
after: len 3, and there is no capacity to ask about
first Some(3) contains 2: true
[1, 2, 3]
into_vec: [1, 2, 3] cap 3
and back: [1, 2, 3]
[1, 2, 3]

See also

Vec::into_boxed_slice in the standard library ↗