Slices
The previous lesson showed how to borrow a whole value with &. Often you only
want part of it: the first three scores, everything after a header row, the
word in the middle of a sentence. Copying that part into a new array or
String would work, but it costs an allocation and a copy every time.
A slice borrows a run of elements that sit next to each other in memory,
without copying them. You already met two kinds: &[T] in the arrays lesson
and &str in the ownership lesson. This lesson looks at both properly, and at
their mutable counterpart, &mut [T].
What a slice is
A slice is a reference to a sequence of elements that some other value owns. It is made of two things: a pointer to the first element and a length. Because it borrows, all the borrowing rules from the previous lesson apply to it.
You make one by indexing with a range and borrowing the result:
fn main() {
let temps = [18, 21, 25, 24, 19, 16, 15];
let midweek = &temps[2..5];
println!("{midweek:?}");
println!("{} days", midweek.len());
}[25, 24, 19]
3 days2..5 includes the start and excludes the end, so midweek holds the elements
at indexes 2, 3 and 4. The type of midweek is &[i32]: a slice of i32s,
with the length stored in the slice rather than in the type. That is the
difference from an array, whose type [i32; 7] fixes the length at compile
time.
Range syntax
Either end of the range can be left out:
fn main() {
let letters = ['a', 'b', 'c', 'd', 'e', 'f'];
println!("{:?}", &letters[1..4]);
println!("{:?}", &letters[1..=4]);
println!("{:?}", &letters[..2]);
println!("{:?}", &letters[4..]);
println!("{:?}", &letters[..]);
println!("{:?}", &letters[3..3]);
}['b', 'c', 'd']
['b', 'c', 'd', 'e']
['a', 'b']
['e', 'f']
['a', 'b', 'c', 'd', 'e', 'f']
[]a..bruns fromaup to, but not including,b.a..=bincludesb...bstarts at the beginning,a..runs to the end, and..is the whole thing.- A range with the same start and end is an empty slice, which is allowed.
These are the same ranges for loops use. A range that reaches past the end,
or whose start is after its end, panics at runtime, as the common mistakes
below show.
Slices as parameters
A function that takes &[T] works with any number of elements from any
source: a whole array, part of one, or a vector. A Vec<T> is a growable list
that owns its elements, covered in the collections lesson; for now, vec![...]
makes one with the elements you list:
fn average(values: &[f64]) -> f64 {
let mut sum = 0.0;
for v in values {
sum += v;
}
sum / values.len() as f64
}
fn main() {
let week = [7.5, 8.0, 6.5, 9.0, 7.0];
let history = vec![6.0, 6.5, 8.5];
println!("{:.2}", average(&week));
println!("{:.2}", average(&week[3..]));
println!("{:.2}", average(&history));
}7.60
8.00
7.00&week is a reference to an array and &history a reference to a vector, but
both are converted to &[f64] automatically, the same way a &String becomes
a &str. Prefer &[T] over &Vec<T> or &[T; N] for parameters, for the
same reason you prefer &str over &String: the function accepts more kinds
of input and gives up nothing.
(This average divides by zero on an empty slice, which gives NaN for
floats. A real one would check values.is_empty() first.)
Looking inside a slice
Slices come with plenty of methods. Some of the most useful:
fn main() {
let queue = ["ana", "ben", "chloe", "dev"];
let people = &queue[..];
println!("{}", people.len());
println!("{}", people.is_empty());
println!("{:?} {:?}", people.first(), people.last());
println!("{:?} {:?}", people.get(2), people.get(10));
println!("{}", people.contains(&"ben"));
println!("{}", people.starts_with(&["ana"]));
let (front, back) = people.split_at(1);
println!("{front:?} {back:?}");
}4
false
Some("ana") Some("dev")
Some("chloe") None
true
true
["ana"] ["ben", "chloe", "dev"]first, last and get return Some(value) or None instead of panicking
when the element doesn't exist, which makes them safe on an empty slice or
with an index you haven't checked. Some and None belong to the Option
type, which has its own lesson. split_at divides a slice into two slices
that sit side by side.
To copy a slice's elements into something you own, use .to_vec(), which
returns a new Vec.
Mutable slices
A mutable slice, &mut [T], lets you change the elements it borrows. Take
it with &mut on a mut array or vector:
fn cap_at(values: &mut [u32], limit: u32) {
for v in values {
if *v > limit {
*v = limit;
}
}
}
fn main() {
let mut volumes = [40, 95, 120, 70, 101];
cap_at(&mut volumes[1..], 100);
println!("{volumes:?}");
}[40, 95, 100, 70, 100]Looping over a &mut [T] gives you a &mut T for each element, so you change
it through *, just like the &mut u32 in the previous lesson. Only the part
of volumes that was sliced could change; index 0 was outside the slice.
You can also assign by index, and slices have methods that rearrange elements in place:
fn main() {
let mut cards = [7, 3, 9, 1, 5];
let hand = &mut cards[..];
hand[0] = 8;
hand.swap(1, 2);
println!("{hand:?}");
hand.sort();
println!("{hand:?}");
hand.reverse();
println!("{hand:?}");
hand[3..].fill(0);
println!("{cards:?}");
}[8, 9, 3, 1, 5]
[1, 3, 5, 8, 9]
[9, 8, 5, 3, 1]
[9, 8, 5, 0, 0]Every change lands in cards itself, because hand never had its own copy.
Note the last println! reads cards, which is only allowed because hand
is no longer used; the borrow rules from the previous lesson apply to slices
as well.
What a slice can't do is change its length. There is no push or remove on
&mut [T], since the memory belongs to someone else. To grow or shrink a list,
you need the owner, like the Vec.
Two mutable parts at once
The borrow rules forbid two &mut borrows of the same array, even for parts
that don't overlap, because the compiler doesn't check ranges. When you need
both halves at once, split_at_mut splits one mutable slice into two that are
guaranteed not to overlap:
fn main() {
let mut scores = [10, 20, 30, 40];
let (left, right) = scores.split_at_mut(2);
left[0] += right[1];
right[0] = 0;
println!("{scores:?}");
}[50, 20, 0, 40]String slices
&str is a slice too: a view into UTF-8 text owned by a String or stored in
the program. Slicing a string uses the same ranges, measured in bytes:
fn main() {
let log_line = String::from("2026-09-26 ERROR disk full");
let date = &log_line[..10];
let level = &log_line[11..16];
let message = &log_line[17..];
println!("[{date}] [{level}] [{message}]");
}[2026-09-26] [ERROR] [disk full]date, level and message all point into log_line; no text was copied.
While they are in use, log_line can't be changed, so a slice can never show
text that has since been modified or freed.
Fixed byte positions are fine for fixed-format text like this. For everything
else, methods on &str find the pieces for you and hand them back as slices:
fn main() {
let sentence = " slices borrow without copying ";
let trimmed = sentence.trim();
println!("[{trimmed}]");
for word in trimmed.split_whitespace() {
println!("{word} ({} bytes)", word.len());
}
println!("{}", trimmed.starts_with("slices"));
}[slices borrow without copying]
slices (6 bytes)
borrow (6 bytes)
without (7 bytes)
copying (7 bytes)
truetrim returns a slice of the same text without the whitespace at either end,
and split_whitespace yields one slice per word.
Because string ranges count bytes, a range must start and end on a character
boundary. With plain ASCII text every byte is a character, but a character
like é takes two bytes, and slicing through the middle of it panics.
Common mistakes
A range past the end
fn main() {
let rolls = [4, 2, 6, 1, 3];
let last_three = &rolls[3..6];
println!("{last_three:?}");
}Slicing checks its bounds at runtime, like indexing does, and panics instead
of reading memory it doesn't own. Use &rolls[2..] to get the last three
elements, or rolls.len() to compute the range.
Slicing through a character
fn main() {
let city = String::from("Zürich");
let first_two = &city[..2];
println!("{first_two}");
}ü takes bytes 1 and 2, so ..2 would cut it in half. The panic message says
exactly which character was in the way. Only slice strings at positions you
know are boundaries, like those found by searching the string or by
split_whitespace, and work character by character with .chars() when you
need to count characters.
Forgetting the &
fn main() {
let numbers = [1, 2, 3, 4];
let middle = numbers[1..3];
println!("{middle:?}");
}numbers[1..3] on its own names the elements themselves, whose count the
compiler can't know, so it can't store them in a variable. A slice always
lives behind a reference: write &numbers[1..3].
Passing & where &mut is needed
fn zero_out(values: &mut [i32]) {
values.fill(0);
}
fn main() {
let mut readings = [3, 1, 4];
zero_out(&readings);
println!("{readings:?}");
}The function asks for a mutable slice, so the call has to lend one:
zero_out(&mut readings).
Summary
- A slice borrows a run of elements without copying them. It is a pointer and
a length, and its type doesn't include the length:
&[T],&mut [T],&str. - Make one with a range:
&a[1..4],&a[1..=4],&a[..2],&a[2..],&a[..]. Ranges past the end panic at runtime. - Take
&[T]in functions that read a list; arrays, parts of arrays and vectors all fit. first,lastandgetreturnSome/Noneinstead of panicking;len,contains,split_atandto_vecare other everyday methods.&mut [T]changes elements in place (*v = ...,swap,sort,reverse,fill) but can't add or remove them.&stris a string slice. String ranges count bytes and must land on character boundaries.
Practise what you read
Challenges that use the ideas from this lesson.