Slices
Slices vs. Owned Collections
Slices (&[T], &mut [T], &str) are non-owning views into contiguous memory, whereas owned collections (Vec<T>, String, Box<[T]>, [T; N]) own their elements and are responsible for allocating, resizing, and dropping them.
Architectural Comparison
| Property | &[T] / &str (Slice Reference) |
Vec<T> / String (Growable Heap Collection) |
Box<[T]> / Box<str> (Boxed Slice) |
[T; N] (Fixed Array) |
|---|---|---|---|---|
| Ownership | Borrowed view (Copy for &[T]) |
Owns heap buffer | Owns heap buffer | Owns inline elements |
| Stack size (64-bit) | 16 bytes (ptr, len) |
24 bytes (ptr, len, cap) |
16 bytes (ptr, len) |
size_of::<T>() * N |
| Can append / resize? | No (can only narrow the view) | Yes (.push(), .extend(), .truncate()) |
No (fixed length after creation) | No (compile-time constant N) |
| Heap allocation | None (points to existing stack/heap/static memory) | Yes (when cap > 0) |
Yes (exact size len, no spare capacity) |
None (stored inline on stack or in parent struct) |
| Drop behavior | No-op (does not drop elements) | Drops all len elements and frees heap buffer |
Drops all len elements and frees heap buffer |
Drops all N elements |
Stack Handles Pointing to Contiguous Sequence Memory:
Vec<T> (24B owner) Box<[T]> (16B owner) &[T] (16B borrowed view)
+------------------+ +------------------+ +------------------+
| ptr | | ptr | | ptr |
| len = 3 | | len = 3 | | len = 2 |
| cap = 4 | +------------------+ +------------------+
+--------+---------+ | |
| v v
v +-----+-----+-----+ (Borrows any subrange of
+-----+-----+-----+-----+ | A | B | C | Vec, Box<[T]>, or [T; N]
| A | B | C |uninit +-----+-----+-----+ without allocating!)
+-----+-----+-----+-----+
Converting Between Slices and Owned Collections
Moving from an owned collection to a slice is a zero-cost O(1) pointer copy, whereas converting a borrowed slice into an owned heap collection performs an O(n) allocation and element clone/copy:
| Direction | Conversion Expression | Cost |
|---|---|---|
Vec<T> / [T; N] -> &[T] |
&vec, vec.as_slice(), &arr |
O(1) zero-copy |
String -> &str |
&s, s.as_str() |
O(1) zero-copy |
&[T] -> Vec<T> |
slice.to_vec() or Vec::from(slice) (requires T: Clone) |
O(n) heap allocation + clone |
&str -> String |
str_slice.to_owned() or String::from(str_slice) |
O(n) heap allocation + byte copy |
Vec<T> -> Box<[T]> |
vec.into_boxed_slice() |
Drops excess capacity (shrink_to_fit) and converts in place |
Box<[T]> -> Vec<T> |
boxed.into_vec() |
O(1) zero-copy (cap set to len) |
&[T] -> [T; N] |
slice.try_into() (requires T: Copy) |
O(N) stack copy if slice.len() == N |
fn main() {
// 1. Owned collection -> Slice (O(1), zero allocation)
let owned_vec = vec![10, 20, 30, 40];
let view: &[i32] = &owned_vec[1..3];
// 2. Slice -> Owned Vec<T> (allocates a new heap buffer for `[20, 30]`)
let cloned_subvec: Vec<i32> = view.to_vec();
// 3. Slice -> Fixed-size array `[i32; 2]` via `TryFrom`
let fixed_pair: [i32; 2] = view.try_into().expect("slice has length 2");
println!("Original Vec: {owned_vec:?}");
println!("Slice view: {view:?}");
println!("Cloned Vec: {cloned_subvec:?}");
println!("Fixed [i32;2]: {fixed_pair:?}");
}
Original Vec: [10, 20, 30, 40]
Slice view: [20, 30]
Cloned Vec: [20, 30]
Fixed [i32;2]: [20, 30]
Shrinking the View vs. Mutating the Collection
Calling .split_first() or reassigning slice = &slice[1..] on a slice reference only advances its local ptr and decrements its len—the underlying Vec<T> or array is untouched:
fn main() {
let backing = vec!["alpha", "beta", "gamma"];
let mut window: &[&str] = &backing;
// Narrow the slice view by 1 element:
window = &window[1..];
println!("Narrowed slice window: {window:?}");
println!("Backing Vec unchanged: {backing:?}");
}
Narrowed slice window: ["beta", "gamma"]
Backing Vec unchanged: ["alpha", "beta", "gamma"]