Slices
A slice is a view into a contiguous sequence — an array, vector, or string. It’s a fat pointer (data + length) that borrows from its source. Slices are how Rust gives you read or write access to part of a collection without copying or transferring ownership.
&[T], &mut [T], &str, ranges
EXAMPLE
// 1) Slicing an array
fn main() {
let arr = [10, 20, 30, 40, 50];
let whole: &[i32] = &arr[..];
let head: &[i32] = &arr[..3]; // 10, 20, 30
let tail: &[i32] = &arr[2..]; // 30, 40, 50
let mid: &[i32] = &arr[1..4]; // 20, 30, 40
println!("{:?} {:?} {:?} {:?}", whole, head, tail, mid);
}
// 2) Slicing a Vec
fn main2() {
let v: Vec<i32> = (1..=10).collect();
let first_five: &[i32] = &v[..5]; // [1, 2, 3, 4, 5]
let after_two: &[i32] = &v[2..]; // [3, 4, 5, 6, 7, 8, 9, 10]
println!("len={} first_five={:?}", v.len(), first_five);
}
// 3) Functions that accept slices — better than Vec<T>
fn sum(xs: &[i32]) -> i32 { // accepts &Vec<i32>, &[i32; N], &mut [i32]
xs.iter().sum()
}
fn main3() {
let v = vec![1, 2, 3, 4];
let a = [10, 20, 30];
println!("{} {}", sum(&v), sum(&a)); // 10 60
}
// 4) Mutable slices
fn double_in_place(xs: &mut [i32]) {
for x in xs.iter_mut() { *x *= 2; }
}
fn main4() {
let mut v = vec![1, 2, 3];
double_in_place(&mut v);
println!("{:?}", v); // [2, 4, 6]
}
// 5) String slices — &str
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &b) in bytes.iter().enumerate() {
if b == b' ' { return &s[..i]; }
}
s
}
fn main5() {
let owned: String = String::from("hello world");
let w: &str = first_word(&owned); // borrows from owned
println!("{}", w); // 'hello'
}
// CAREFUL: byte slicing &s[..i] panics if i is not on a UTF-8 char boundary.
// Use s.char_indices() or s.split_whitespace() for safe text iteration.
// 6) Iteration patterns
fn examples(v: &[i32], v2: &mut [i32]) {
for x in v { /* &i32 */ }
for x in v.iter() { /* &i32 */ }
for x in v2.iter_mut() { *x += 1; } // &mut i32
for (i, x) in v.iter().enumerate() { /* */ }
let total: i32 = v.iter().sum();
let evens: Vec<&i32> = v.iter().filter(|x| **x % 2 == 0).collect();
}
// 7) Split into halves — useful for divide and conquer + no overlap aliasing
fn main7() {
let mut v = vec![1, 2, 3, 4, 5, 6];
let (left, right) = v.split_at_mut(3);
left[0] = 100;
right[0] = 200;
// No borrow checker fight: split_at_mut hands out non-overlapping &mut slices.
println!("{:?}", v); // [100, 2, 3, 200, 5, 6]
}
// 8) Windows and chunks — overlap or non-overlap
fn main8() {
let v = vec![1, 2, 3, 4, 5];
for w in v.windows(3) { println!("{:?}", w); } // [1,2,3] [2,3,4] [3,4,5]
for c in v.chunks(2) { println!("{:?}", c); } // [1,2] [3,4] [5]
}
// 9) Slice as a fat pointer
// &[T] = (data: *const T, len: usize) — 16 bytes on 64-bit
// &mut [T] = (data: *mut T, len: usize)
//
// You can construct one from a raw pointer (unsafe):
use std::slice;
fn from_raw_unsafe<'a>(ptr: *const i32, len: usize) -> &'a [i32] {
unsafe { slice::from_raw_parts(ptr, len) }
}
// 10) Out-of-bounds — panics by default, use .get() for non-panicking
fn main10() {
let v = vec![10, 20, 30];
let _ = v[5]; // panic: index out of bounds
if let Some(x) = v.get(5) { /* */ } // None — safe
}
// 11) Slice methods cheat sheet
// len, is_empty, first, last, get(i)
// iter, iter_mut, into_iter (for owned)
// contains(&x), binary_search(&x)
// sort, sort_by, sort_unstable — in-place
// reverse, fill(v), rotate_left(n), rotate_right(n)
// join(sep), concat
// split, splitn, rsplit, split_at_mut
// windows(n), chunks(n), chunks_exact(n)
// copy_from_slice, clone_from_slice — bulk assignment
//
// And via traits:
// to_vec() — owned copy
// to_owned() — generic
// chunks_exact_mut(n).remainder()
// 12) Lifetimes — slices borrow from their source
fn longest<'a>(a: &'a [i32], b: &'a [i32]) -> &'a [i32] {
if a.len() >= b.len() { a } else { b }
}
// Caller's data must outlive both inputs. The compiler enforces it.
// 13) When to take &str vs &String vs String
// • Function parameter: &str ← accepts &String automatically via deref coercion
// • Field that owns: String
// • Return type for slicing into self: &str
// 14) Common bugs
// • &arr[5..3] → panic 'slice index starts after end'
// • s[..i] where i is mid-UTF-8 → panic 'byte index N is not a char boundary'
// • Multiple &mut slices of the same range → split_at_mut to satisfy the borrow checker
// • Storing a &[T] longer than the source → 'borrowed value does not live long enough'
// • Using vec.iter() and trying to mutate items → use iter_mut()
// • &Vec<T> instead of &[T] as a param → less flexible, accepts only Vec
Why it matters
A slice is a borrowed view, not an owned collection. Take &[T] and &str in function signatures so callers can pass Vec, array, or owned String equally well, and reach for split_at_mut, windows, or chunks when the borrow checker is stopping you from carving up a slice the way you need.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
let v = vec![1, 2, 3, 4, 5];
let middle: &[i32] = &v[1..4];
for x in middle { println!("{x}"); }
Try it Yourself »
Discussion
Loading…