title: Slices

A slice is a reference to a contiguous sequence of elements in a collection. It lets you borrow a portion of an array or vector without copying it.

Syntax

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];  // borrows elements at index 1 and 2 -> [2, 3]

The type of a slice is &[T] (or &mut [T] for mutable slices).

Range syntax

The start is inclusive and the end is exclusive by default. For an array [10, 20, 30, 40, 50]:

&a[1..4]   // [20, 30, 40]    — index 1, 2, 3 (NOT 4)
&a[1..=4]  // [20, 30, 40, 50] — index 1, 2, 3, 4 (inclusive end with =)
&a[..3]    // [10, 20, 30]    — from start up to (not including) 3
&a[2..]    // [30, 40, 50]    — from index 2 to end
&a[..]     // [10, 20, 30, 40, 50] — entire collection

[!info] Why is the end exclusive? This convention comes from Edsger Dijkstra’s 1982 paper “Why numbering should start at zero.” His argument: with an exclusive end, the length of a range is always end - start (e.g. 4 - 1 = 3 elements), and adjacent ranges compose cleanly — [0..4] and [4..8] cover [0..8] with no overlap or gap. The alternative (both inclusive) would require [i..i-1] to express an empty range, which breaks at index 0. Most modern languages (Python, Go, Java) adopted the same convention for these reasons.

String slices

String slices (&str) work the same way:

let s = String::from("hello world");
let hello = &s[0..5];  // "hello"
let world = &s[6..11]; // "world"

String literals are already slices — &str is a slice pointing into the binary.

Slices in function signatures

Prefer &[T] over &Vec<T> and &str over &String in function parameters — they’re more flexible since they accept both owned types and slices:

fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}
 
let v = vec![1, 2, 3];
sum(&v);        // works with Vec
sum(&v[1..]);   // works with a slice of Vec

Key points

  • Slices don’t own data, they borrow it
  • The borrow checker ensures the original data outlives the slice
  • Slices carry both a pointer and a length