Rust Slices
A slice lets you borrow a section of an array or vector — a “view” into part of it — without copying the data or taking ownership. If you’ve used borrowing with a single value, a slice is the same idea applied to a range of elements.
Slicing an array
Section titled “Slicing an array”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // elements at index 1, 2, 3 println!("{:?}", middle);}Output:
[20, 30, 40]1..4 is the same kind of range you saw in for loops — it starts at index 1 and stops before index 4, so you get indices 1, 2, and 3.
Slice range shorthand
Section titled “Slice range shorthand”A few shortcuts that come up often:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // from the start up to index 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // from index 2 to the end -> [30, 40, 50] println!("{:?}", &numbers[..]); // the whole thing -> [10, 20, 30, 40, 50]}String slices
Section titled “String slices”You’ve actually already been using slices — &str is a slice of a String (or of another string)! This is why the two types work so well together:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}Why slices instead of copying?
Section titled “Why slices instead of copying?”You could imagine solving this by copying out a new Vec or String for the section you want. Slices avoid that entirely — they just point at part of the existing data, the same way a reference (&) points at a single value. That means no extra allocation, no copying, and the borrowing rules you already know from the Borrowing page still apply.
A function that takes a slice
Section titled “A function that takes a slice”Slices are especially useful as function parameters, because they accept both arrays and vectors:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] accepts a slice from either an array or a Vec — one function, both call sites work.
Changing data through a mutable slice
Section titled “Changing data through a mutable slice”So far our slices have only read data. A mutable slice, &mut [T], lets a function change the items it was given, but only those items:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // only index 1 and 2 println!("{:?}", scores);
double_all(&mut scores); // the whole vector println!("{:?}", scores);}Output:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]The first call passes &mut scores[1..3], so double_all only sees indices 1 and 2. Everything outside that range is safe from it. Inside the function, the slice starts at index 0 again: to double_all, it’s just a list of two numbers.
This is safe because of a rule you’ll meet properly on the Borrowing page: while the function holds a &mut slice, nothing else can use those items, so two parts of your code can never change them at the same time. The vector also has to be declared mut, or Rust won’t let you borrow it mutably.
Practice
Section titled “Practice”1. Split in half
Section titled “1. Split in half”Make the vector vec![3, 6, 9, 12, 15, 18]. Print its first half and its second half as two separate slices. Hint: compute let mid = numbers.len() / 2; and use ..mid and mid...
Show solution
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("First half: {:?}", first_half); println!("Second half: {:?}", second_half);}Output:
First half: [3, 6, 9]Second half: [12, 15, 18]Because mid is calculated from len(), this keeps working if you add or remove numbers. With an odd length, the extra item ends up in the second half.
2. Largest in any list
Section titled “2. Largest in any list”Write a function largest(numbers: &[i32]) -> i32 that returns the biggest number. Call it with an array, with a vector, and with a slice of just the first item of the vector. Hint: start with numbers[0] and loop over the rest, like you’d do with an array.
Show solution
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Output:
93510Looping over a slice hands you a reference to each item (&i32), not the number itself. *n means “the value n points to”, so *n > biggest compares two plain numbers. This version panics on an empty slice, because numbers[0] doesn’t exist. Later you’ll learn to return an Option for that case.
- A slice (
&[T]) borrows a section of an array or vector without copying it. 1..4selects indices 1 through 3; leave off either side of..to go from the start or to the end.&stris itself a slice of text — that’s why strings and slices share so much behavior.- Prefer
&[T]as a function parameter type when you want to accept both arrays and vectors. - A mutable slice (
&mut [T]) lets a function change just the items it was given.
Quick check
Section titled “Quick check”Quick check
1. What does a slice let you do with part of an array or vector?
2. Which indices does &numbers[1..4] include?
3. Why is &[i32] a good choice for a function parameter type?
Score: 0 / 3
Read more
Section titled “Read more”- The Slice Type — The Rust Book: builds up to slices step by step with a “first word of a string” example.
- Arrays and Slices — Rust by Example: runnable examples of borrowing arrays as slices.
slice— standard library docs: the many methods slices have, likefirst,split_at,containsandsort.- Slice types — The Rust Reference: the precise definition, if you want to know exactly what
[T]and&[T]mean.