---
title: "9.6 Slices"
description: "Rust slices explained: borrowing a view of part of a sequence with range syntax, the &[T] type, and why functions take slices instead of arrays."
url: "https://learnrust.net/chapter-9/slices/"
last_updated: "2026-06-12"
---

# 9.6 Slices

Lesson [5.4](https://learnrust.net/chapter-5/introduction-to-str/) made a promise it called "carving views by range": making a view of *part* of something, like the first word of a sentence, and it said the machinery lived in chapter 9. You now own every piece of that machinery. A partial view is just a reference with an opinion about where to start and stop, and Rust calls it a **slice**.

Strings are where slices shine brightest, and they get the next lesson. Today builds the concept on something simpler, and that something needs a one-minute introduction of its own.

## A sequence to slice

Chapter 18 properly teaches Rust's collection types. We're borrowing the smallest one a chapter early, purely as slicing material: the **array**, a fixed-length run of values of one type, written in square brackets.

```rust
fn main() {
    let temperatures = [12, 14, 15, 13, 11];
    println!("{temperatures:?}");
    println!("first: {}", temperatures[0]);
}
```

```
[12, 14, 15, 13, 11]
first: 12
```

The type is written `[i32; 5]`: five `i32`s, length included in the type, fixed at compile time forever. Because the size is known up front, an array lives whole in the stack frame (lesson [8.2](https://learnrust.net/chapter-8/the-stack-and-the-heap/): no heap, no handle, just five boxes in a row). `temperatures[0]` reads one element, and counting starts at zero, which you'll be used to by chapter 18. Printing uses `{:?}` from lesson 5.5, the programmer's view. That's all the array we need today; growable sequences, the questions you're already forming about them, and arrays' own fine print all belong to chapter 18.

## Carving a view

A slice expression is a borrow with a range attached, using lesson [7.5](https://learnrust.net/chapter-7/for-loops-and-ranges/)'s half-open range syntax:

```rust
fn main() {
    let temperatures = [12, 14, 15, 13, 11];

    let middle = &temperatures[1..4];
    println!("{middle:?}");
    println!("{} readings", middle.len());
}
```

```
[14, 15, 13]
3 readings
```

Read `&temperatures[1..4]` aloud: *borrow a view of `temperatures`, from element 1 up to but not including element 4.* Same half-open convention as a `for` loop's range, and the same off-by-one protection: `1..4` has length 3, and the end index is the first element you *don't* get.

The ranges come with the shorthands you'd hope for. Omit the start to begin at the beginning, the end to run to the end, or both to view everything:

```rust
let first_two = &temperatures[..2];   // [12, 14]
let last_two = &temperatures[3..];    // [13, 11]
let everything = &temperatures[..];   // all five
```

The type of these views is written `&[i32]`: a slice of `i32`s. Notice what's missing compared to `[i32; 5]`: the length. A slice's length is whatever the range made it, known at runtime, carried inside the slice itself, which is why `.len()` works on one. Under the hood a slice is lesson 8.2's String handle stripped to two fields: the address where the view starts, plus a length. No capacity, because a view doesn't own storage; and no caring whether the viewed elements live on the stack (like this array) or the heap (like a String's text). A window is a window.

All of chapter 9 applies, because a slice *is* a reference. It borrows the array, so while a slice is alive the one-writer rule protects it, and rule 2 guarantees it can't outlive the array it watches. Nothing new to learn; the rules just got a third spelling: `&T`, `&mut T`, and now `&[T]`.

## Why functions take slices

Here's the payoff, and it's the 5.4 parameter argument generalized. Write a function against the *view type* and it accepts any contiguous run of `i32`s, whole or partial, any length:

```rust
fn sum(values: &[i32]) -> i32 {
    let mut total = 0;
    let mut i = 0;
    while i < values.len() {
        total += values[i];
        i += 1;
    }
    total
}

fn main() {
    let temperatures = [12, 14, 15, 13, 11];
    println!("week total: {}", sum(&temperatures));
    println!("weekend total: {}", sum(&temperatures[3..]));
}
```

```
week total: 65
weekend total: 24
```

One function, called once with the whole array and once with a two-element slice of it. Had the parameter been `[i32; 5]`, the second call would be a type error (a `[i32; 2]` is a different type; the length is part of the type, remember), and so would every future call with six readings. `&[i32]` doesn't care: any window onto `i32`s, however wide, from whatever it was carved out of.

One small magic trick to flag before it slips past: `sum(&temperatures)` passes `&[i32; 5]` where `&[i32]` was requested, and the compiler quietly converts. You've seen this kindness before, lending a `&String` where `&str` was wanted, and next lesson finally names the mechanism. (The index-and-`while` body, meanwhile, is honest but clunky; chapter 19's iterators will retire it with prejudice.)

> **Best practice**
>
> Functions that read a sequence should take a slice (`&[i32]`), not an array type, for exactly the reason text parameters take `&str`: the function gets more useful, and callers lend instead of give.

## When the range is wrong

A slice range is checked against the real length, and an out-of-range slice is a panic, the runtime refusal you know from lesson [5.6](https://learnrust.net/chapter-5/parsing-strings-into-numbers/). To see one, we need the range to come from input (write `[..9]` on a five-element array with literals involved and you've learned by now, from lessons 3.1 and 4.4, that this compiler does arithmetic at compile time and may refuse outright):

```rust
use std::io;

fn main() {
    let temperatures = [12, 14, 15, 13, 11];

    let mut count = String::new();
    io::stdin().read_line(&mut count).expect("failed to read line");
    let count: usize = count.trim().parse().expect("not a number");

    let recent = &temperatures[..count];
    println!("first {count} readings: {recent:?}");
}
```

Type `3` and you get `first 3 readings: [12, 14, 15]`. Type `9`:

```
thread 'main' (2304338) panicked at src/main.rs:10:31:
range end index 9 out of range for slice of length 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

The message has everything a 3 a.m. debugger needs: what was asked (end index 9), what was available (length 5), and where (line 10). A view nine elements wide onto five elements would be a window into memory that isn't yours to watch; Rust's choice here is to stop the program rather than let it read mystery bytes. Chapter 12 teaches the graceful alternatives; until then, check lengths before you carve.

## Quiz time

**Question #1**

For `let scores = [10, 20, 30, 40];`, give the contents and `len()` of each slice, no compiler allowed:

a) `&scores[..2]`
b) `&scores[2..]`
c) `&scores[1..2]`
d) `&scores[..]`

<details class="solution">
<summary>Show solution</summary>

a) `[10, 20]`, len 2. b) `[30, 40]`, len 2. c) `[20]`, len 1 (half-open: element 1, stop before 2; a one-element slice is still a slice). d) `[10, 20, 30, 40]`, len 4.

</details>

**Question #2**

In the input-driven program above, describe what happens for each typed input: `5`, `0`, `six`.

<details class="solution">
<summary>Show solution</summary>

`5`: prints all five readings; an end index equal to the length is the largest legal one (half-open ranges again). `0`: prints `first 0 readings: []`; an empty slice is legal and has `len()` 0. `six`: never reaches the slice. `parse` fails and the `expect` panics with your message `not a number` (lesson 5.6's machinery).

</details>

**Question #3**

Write `fn last(values: &[i32]) -> i32` returning the final element. Then the follow-up: what does your function do if someone passes an empty slice, and how do you know?

<details class="solution">
<summary>Show solution</summary>

```rust
fn last(values: &[i32]) -> i32 {
    values[values.len() - 1]
}
```

For an empty slice, `values.len()` is 0, and `0 - 1` on a `usize` can't be -1: it's lesson 4.4's underflow, which panics in debug builds before the indexing even gets a chance to complain. Either way the program stops rather than reads out of range. (A `last` that *gracefully* handles emptiness wants to return "maybe an i32," and that type, `Option`, is chapter 11's headline.)

</details>

Slices of numbers are the rehearsal. The performance is the type you've been pronouncing since chapter 5 without seeing its full face: `&str`, the *string slice*. Next lesson, 5.4's IOU gets paid in full.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
