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.