Slices

Immutable Slices

An immutable slice (or shared slice), written &[T] for element sequences and &str for UTF-8 text, provides read-only, bounds-checked access to a contiguous range of elements without copying or taking ownership of the underlying collection.

Key Properties of &[T]

  1. Read-only guarantee: While an immutable slice &[T] is active, neither the slice nor the underlying owner (Vec<T> or [T; N]) can modify the elements or reallocate the buffer.
  2. Freely copyable (Copy): Because &[T] is a shared 16-byte fat pointer ((ptr, len)), &[T] implements Copy for every type T—even when T itself is not Copy (such as &[String])! Copying or sub-slicing &[T] only copies the 16-byte (ptr, len) pair on the stack.
  3. Zero-cost abstraction over any contiguous buffer: A single function accepting &[T] can inspect stack arrays [T; N], heap vectors Vec<T>, boxed slices Box<[T]>, or empty slices &[].
fn print_first_and_last(slice: &[String]) {
    // `slice` is `Copy` even though `String` is not `Copy`!
    let copy_of_slice_ref: &[String] = slice;

    if let (Some(first), Some(last)) = (copy_of_slice_ref.first(), slice.last()) {
        println!("First: '{first}', Last: '{last}' (total = {})", slice.len());
    } else {
        println!("Slice is empty");
    }
}

fn main() {
    let tags = [
        String::from("rust"),
        String::from("systems"),
        String::from("memory-safe"),
    ];

    print_first_and_last(&tags);
    print_first_and_last(&tags[0..2]);
    print_first_and_last(&[]);
}
First: 'rust', Last: 'memory-safe' (total = 3)
First: 'rust', Last: 'systems' (total = 2)
Slice is empty

Non-Mutating Inspection and Search Methods on &[T]

Immutable slices support a rich suite of read-only query, search, and iteration methods:

Method Signature / Example What It Returns
slice.len() / slice.is_empty() s.len() Number of elements (usize) / true if len == 0
slice.get(idx_or_range) s.get(2) / s.get(1..3) Option<&T> or Option<&[T]> (None if out of bounds)
slice.first() / slice.last() s.first() Option<&T> for index 0 or index len - 1
slice.split_first() / split_last() s.split_first() Option<(&T, &[T])> splitting head from tail
slice.split_at(mid) s.split_at(2) (&[T], &[T]) splitting [..mid] and [mid..]
slice.contains(&val) s.contains(&42) true if any element equals val ($O(n)$ linear scan)
slice.starts_with(sub) / ends_with(sub) s.starts_with(&[1, 2]) true if prefix or suffix matches sub
slice.binary_search(&val) s.binary_search(&42) Ok(idx) if found or Err(insert_idx) ($O(\log n)$ on sorted slice)
slice.windows(n) s.windows(2) Iterator over overlapping sub-slices &[T] of length n
slice.chunks(n) / chunks_exact(n) s.chunks(3) Iterator over non-overlapping sub-slices &[T] of length up to n
fn main() {
    let prices: &[u32] = &[10, 20, 35, 50, 80];

    // 1. Sliding windows of size 2 to compute consecutive differences:
    let diffs: Vec<u32> = prices.windows(2).map(|w| w[1] - w[0]).collect();
    println!("Consecutive price jumps: {diffs:?}");

    // 2. Fast O(log n) binary search on a sorted slice:
    println!("binary_search(35): {:?}", prices.binary_search(&35));
    println!("binary_search(40): {:?}", prices.binary_search(&40)); // Err(3) = insertion index!
}
Consecutive price jumps: [10, 15, 15, 30]
binary_search(35): Ok(2)
binary_search(40): Err(3)

Iterating Over an Immutable Slice (for x in slice)

Iterating over slice: &[T] yields shared references &T to each element. For Copy element types (such as i32), you can either pattern-match the reference in the loop header (for &n in slice) or call slice.iter().copied():

fn main() {
    let values: &[i32] = &[5, 10, 15];

    let mut sum = 0;
    for &n in values {
        // `&n` destructures `&i32` so `n` has type `i32`:
        sum += n;
    }

    println!("Sum: {sum}");
}
Sum: 30