Skip to content
Select theme

What is a slice, and why is it a borrow?

Short answer: A slice type — &[T] for a run of elements, &str for a run of text — is not a container. It is a borrowed view onto data someone else owns: a pointer plus a length, stored in two machine words. Because it points into an owner’s memory instead of holding a copy, it is a borrow like any other reference, and the borrow checker holds you to the owner’s rules for as long as the slice is in use.

Start from a fresh project — cargo new slice-borrow, then replace src/main.rs with the whole of each file below.

A shared reference to a single value is one machine word wide — just an address. A slice carries a second word along with it:

&i32 is 8 bytes
&[i32] is 16 bytes

The 16 bytes are a pointer to the first element and the number of elements. That second word is why middle.len() below is free — the length is stored in the slice itself — and why a slice has no capacity: growing and shrinking are the owner’s business, not the window’s.

The slice is a window, not a copy. It shows whatever elements the owner holds between the two indices you asked for:

middle = [40, 30], len = 2

scores still owns all four numbers; middle is a &[i32] that points at elements 1 and 2 of scores and remembers it has two of them.

Because &[i32] is a borrowed window rather than a specific container, the same function accepts a whole Vec, a fixed-size array, or another slice. Rust shortens &Vec<i32> and &[i32; 3] to &[i32] at the call:

sum(&vec) = 10
sum(&array) = 60
sum(middle) = 5

That is the reason API signatures around the standard library say &[T] instead of &Vec<T>: the parameter accepts every kind of sequence there is, and still borrows the data instead of taking it.

The slice holds no elements, so nothing about the data belongs to it — the Vec owns the numbers, and the slice is a borrow of part of them. Everything the rules of borrowing say therefore applies to slices too:

error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let all = &scores[..];
| ------ immutable borrow occurs here
4 | scores.push(0);
| ^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("{all:?}");
| --- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.

&scores[..] is the whole vector seen as a slice — the same borrow that &scores creates, with a length attached. While all is still used, the borrow is still live, so push is refused: it needs &mut scores, and growing a vector can move its elements to new memory, which would leave the slice’s pointer aiming at freed memory. A copied slice could go stale silently; a borrowed one cannot, and the error above is the borrow checker collecting on that promise.

The way out is the same as for any borrow that lasts too long — let it end before you mutate:

total = 80, scores = [10, 40, 30, 0]

Here the borrow of scores ends on the sum line, because total — an i32, not a reference — is what survives. (How to end a borrow early covers the general techniques.)

Text works the same way: a &str is a slice of bytes pointed into a String or a string literal, which is why String versus &str is an ownership question and not a style one.