Skip to content

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.

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]
▶ Try it Yourself

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.

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]
}
▶ Try it Yourself

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"
}
▶ Try it Yourself

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.

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));
}
▶ Try it Yourself

&[i32] accepts a slice from either an array or a Vec — one function, both call sites work.

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]
▶ Try it Yourself

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.

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]
▶ Try it Yourself

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.

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:

9
35
10
▶ Try it Yourself

Looping 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..4 selects indices 1 through 3; leave off either side of .. to go from the start or to the end.
  • &str is 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

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