Vec::into_boxed_slice¶
Level: reference · for working programmers
One line: Consume the vector and give back a Box<[T]> — the buffer, exactly sized.
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::shrink_to_fit— the same shrink, staying aVecVec::leak— the other consuming conversion to a slice- The
Box— what the pointer half is Vec::as_slice— the borrowing versionAllocator::shrink— the shrink half, one layer down