Slices

Slice Indexing

Slice indexing accesses a single element or a contiguous subslice of [T] or str using the square-bracket indexing operators (slice[index] and &slice[range]) or fallible lookup methods (.get(index) and .get_mut(index)).

The Index and SliceIndex Traits

Under the hood, slice[idx] desugars to *std::ops::Index::index(&slice, idx) (or IndexMut::index_mut in a mutable place expression). What slice[idx] returns depends on the type of idx, which is governed by the std::slice::SliceIndex<[T]> trait:

Index Expression Type of Index Return Type on [T] Return Type on str Behavior on Out-of-Bounds
s[i] usize T (by dereference &T) Not allowed (str cannot be indexed by single usize) Panics at runtime
&s[a..b] Range<usize> &[T] (length b - a) &str (must lie on UTF-8 char boundaries) Panics if a > b or b > s.len()
&s[a..=b] RangeInclusive<usize> &[T] (length b - a + 1) &str Panics if a > b or b >= s.len()
&s[a..] RangeFrom<usize> &[T] (from a to end) &str Panics if a > s.len()
&s[..b] RangeTo<usize> &[T] (from 0 to b) &str Panics if b > s.len()
&s[..=b] RangeToInclusive<usize> &[T] (from 0 to b inclusive) &str Panics if b >= s.len()
&s[..] RangeFull &[T] (full slice) &str Never panics
Index and Range Positions for `s = [10, 20, 30, 40, 50]` (len = 5):

  Boundary:  0      1      2      3      4      5
             +------+------+------+------+------+
  Elements:  |  10  |  20  |  30  |  40  |  50  |
             +------+------+------+------+------+
  Element i:   s[0]   s[1]   s[2]   s[3]   s[4]
  &s[1..4]:         |<--- [20, 30, 40] --->|
  &s[3..]:                        |<-- [40, 50] ->|

Single-Element and Range Indexing in Practice

fn main() {
    let numbers = [10, 20, 30, 40, 50];
    let slice: &[i32] = &numbers;

    // 1. Single-element indexing (`usize` returns the element at that offset)
    let first: i32 = slice[0];
    let last: i32 = slice[slice.len() - 1];

    // 2. Half-open and inclusive range subslicing
    let middle: &[i32] = &slice[1..4];
    let inclusive: &[i32] = &slice[1..=3];
    let prefix: &[i32] = &slice[..2];
    let suffix: &[i32] = &slice[3..];

    println!("first = {first}, last = {last}");
    println!("middle    (&slice[1..4])  = {middle:?}");
    println!("inclusive (&slice[1..=3]) = {inclusive:?}");
    println!("prefix    (&slice[..2])   = {prefix:?}");
    println!("suffix    (&slice[3..])   = {suffix:?}");
}
first = 10, last = 50
middle    (&slice[1..4])  = [20, 30, 40]
inclusive (&slice[1..=3]) = [20, 30, 40]
prefix    (&slice[..2])   = [10, 20]
suffix    (&slice[3..])   = [40, 50]

Safe Non-Panicking Access with .get() and .get_mut()

Whenever an index or range originates from untrusted input or might exceed the slice length, use .get(index) or .get_mut(index). Both accept any usize or range type implementing SliceIndex and return Option<&Output> (or Option<&mut Output>) instead of panicking:

fn main() {
    let mut scores = [85, 92, 78];
    let s: &mut [i32] = &mut scores;

    // Safe single-element lookup
    println!("s.get(1)  = {:?}", s.get(1));
    println!("s.get(10) = {:?}", s.get(10));

    // Safe range lookup
    println!("s.get(1..3) = {:?}", s.get(1..3));
    println!("s.get(1..9) = {:?}", s.get(1..9));

    // Safe in-place mutation via `.get_mut()`
    if let Some(slot) = s.get_mut(2) {
        *slot += 10;
    }
    println!("Updated slice: {s:?}");
}
s.get(1)  = Some(92)
s.get(10) = None
s.get(1..3) = Some([92, 78])
s.get(1..9) = None
Updated slice: [85, 92, 88]

Why Slices Are Indexed Only by usize

Rust requires all slice indices to be of type usize—the pointer-sized unsigned integer for the target CPU architecture (64-bit on modern desktop and server platforms). Signed integers (i32) or fixed-width integers (u32, u64) cannot be used directly as slice indices because negative indices have no meaning in zero-based memory offset arithmetic and mismatched integer widths would require implicit casts.