Slices in Rust
Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection. A slice is a kind of reference, so it does not take ownership of the data it points to.
What Is a Slice?
A slice is a reference to a contiguous portion of a collection — like a window into an array, Vec, or String. Instead of copying data, a slice borrows a piece of it. The type of a slice is written as &[T] for sequences, or &str for string slices.
String Slices (`&str`)
The most common slice in Rust is the string slice. A &str is a reference to a portion of a String (or a string literal stored in the binary).
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"
let world = &s[6..11]; // "world"
println!("{} {}", hello, world);
}Range Syntax
Rust provides several range syntaxes for slicing. The end index is exclusive by default; use ..= to make it inclusive.
Syntax | Meaning |
|---|---|
| indices 0, 1, 2, 3, 4 (end exclusive) |
| indices 0, 1, 2, 3, 4 (end inclusive) |
| from the start up to index 4 |
| from index 3 to the end |
| the entire string or collection |
fn main() {
let s = String::from("hello world");
let from_start = &s[..5]; // "hello"
let to_end = &s[6..]; // "world"
let everything = &s[..]; // "hello world"
println!("{}", from_start);
println!("{}", to_end);
println!("{}", everything);
}Why `&str` Over `&String`?
Consider a function that finds the first word in a string. An early approach might return a usize index:
// Fragile: the index becomes meaningless if the String changes
fn first_word_index(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word_index(&s); // word = 5
s.clear(); // s is now ""
// word is still 5, but s is empty — the index is now invalid!
println!("{}", word);
}The slice version is much safer. The borrow checker ensures the slice stays valid as long as it is used:
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("First word: {}", word);
// s.clear(); // compile error! cannot mutate 's' while 'word' holds a borrow
}Array and Vec Slices (`&[T]`)
The same slice concept applies to arrays and Vec<T>. The slice type is &[T].
fn main() {
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // [2, 3]
println!("{:?}", slice);
let v: Vec<i32> = vec![10, 20, 30, 40, 50];
let v_slice = &v[2..4]; // [30, 40]
println!("{:?}", v_slice);
}The `[T]` vs `&[T]` Distinction
[T] by itself is an unsized (dynamically sized) type — you cannot store it directly in a variable because the compiler does not know its size at compile time. You always work with it behind a reference: &[T] (immutable slice) or &mut [T] (mutable slice). Both are fat pointers carrying a data address and a length.
Slice Borrowing Rules
Slices follow the same borrowing rules as all references in Rust:
You can have any number of immutable slices (
&[T]) at the same time.You can have exactly one mutable slice (
&mut [T]) at a time — and no other references.A mutable slice cannot coexist with any other borrow of the same data.
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
// OK: multiple immutable borrows at once
let s1 = &v[0..2];
let s2 = &v[3..5];
println!("{:?} {:?}", s1, s2);
// OK: one mutable borrow (previous immutable borrows are no longer used)
let ms = &mut v[0..3];
ms[0] = 99;
println!("{:?}", ms);
}Mutable Slices (`&mut [T]`)
A &mut [T] slice lets you modify the elements it refers to. This is useful when you want to sort or transform part of a collection in place.
fn double_elements(slice: &mut [i32]) {
for x in slice.iter_mut() {
*x *= 2;
}
}
fn main() {
let mut data = [1, 2, 3, 4, 5];
double_elements(&mut data[1..4]); // doubles indices 1, 2, 3
println!("{:?}", data);
}Prefer `&[T]` Over `&Vec<T>` in Function Parameters
Function parameters should use slice types rather than references to owned types. This makes your functions more flexible: callers can pass arrays, slices, or Vecs without changes on their end.
// Less flexible — only accepts &Vec<i32>
fn sum_vec(v: &Vec<i32>) -> i32 {
v.iter().sum()
}
// More flexible — accepts &Vec<i32>, &[i32], arrays, and sub-slices
fn sum_slice(v: &[i32]) -> i32 {
v.iter().sum()
}
fn main() {
let v = vec![1, 2, 3];
let a = [4, 5, 6];
println!("{}", sum_slice(&v)); // works with Vec
println!("{}", sum_slice(&a)); // works with array
println!("{}", sum_slice(&v[1..])); // works with a slice of a Vec
}Common Slice Methods
fn main() {
let mut v = vec![5, 3, 8, 1, 9, 2];
let s = v.as_mut_slice();
// Length and emptiness
println!("len: {}", s.len());
println!("empty: {}", s.is_empty());
// First and last element — returns Option<&T>
println!("first: {:?}", s.first());
println!("last: {:?}", s.last());
// Membership
println!("contains 8: {}", s.contains(&8));
// Split at an index — returns two sub-slices
let (left, right) = s.split_at(3);
println!("left: {:?}, right: {:?}", left, right);
}Sorting and Searching
fn main() {
let mut nums = [4, 2, 7, 1, 9, 3];
nums.sort();
println!("sorted: {:?}", nums);
// binary_search requires a sorted slice
match nums.binary_search(&7) {
Ok(index) => println!("found 7 at index {}", index),
Err(index) => println!("7 not found; would insert at {}", index),
}
}Windows and Chunks
Two iterator adapters that are unique to slices are .windows(n) and .chunks(n).
.windows(n)— produces overlapping sub-slices of length n, advancing by one each step.chunks(n)— produces non-overlapping sub-slices of length n (the last chunk may be shorter)
fn main() {
let data = [1, 2, 3, 4, 5];
println!("Windows of 3:");
for w in data.windows(3) {
println!(" {:?}", w);
}
println!("Chunks of 2:");
for c in data.chunks(2) {
println!(" {:?}", c);
}
}Iterating Over Slices
fn main() {
let scores = [88, 92, 75, 100, 63];
// Immutable iteration — borrows each element
for score in scores.iter() {
print!("{} ", score);
}
println!();
// Mutable iteration — can modify each element in place
let mut grades = [70, 80, 90];
for g in grades.iter_mut() {
*g += 5; // curved by 5 points
}
println!("{:?}", grades);
// enumerate — get index alongside value
for (i, &val) in scores.iter().enumerate() {
println!("scores[{}] = {}", i, val);
}
}Summary
&stris a string slice — a reference to part (or all) of aStringor string literal.&[T]is a sequence slice — a reference to a contiguous run of elements in an array orVec.Slices carry a pointer and a length; they do not own data and make no heap allocations.
Use
&strover&Stringand&[T]over&Vec<T>in function parameters for maximum flexibility.The same borrow rules apply: one mutable OR many immutable references at any given time.
Rich built-in methods:
.len(),.sort(),.binary_search(),.windows(),.chunks(),.split_at(), and more.