Skip to content

Slices

A slice is a reference to a contiguous sequence of elements in a collection. It does not own the data — it borrows a view into existing memory.

There are two common slice types:

  • &[T] — a slice of any array or vector of type T
  • &str — a string slice; a view into UTF-8 string data

Because a slice is always a reference, its lifetime is tied to the collection it borrows from.

Given an array or vector, you create a slice using range syntax inside square brackets:

fn main() {
let numbers = [10, 20, 30, 40, 50];
let all: &[i32] = &numbers[..]; // entire array
let first_three: &[i32] = &numbers[..3]; // indices 0, 1, 2
let middle: &[i32] = &numbers[1..4]; // indices 1, 2, 3
let last_two: &[i32] = &numbers[3..];// indices 3, 4
println!("all: {:?}", all);
println!("first three: {:?}", first_three);
println!("middle: {:?}", middle);
println!("last two: {:?}", last_two);
}

Range syntax summary:

SyntaxMeaning
[..]entire collection
[..n]first n elements (indices 0 to n-1)
[m..n]indices m to n-1
[m..]from index m to the end

A string slice (&str) is a reference to a portion of a String or a string literal. String literals themselves are &str — they are slices of read-only data baked into the binary.

fn main() {
let s = String::from("hello world");
let hello: &str = &s[..5]; // "hello"
let world: &str = &s[6..]; // "world"
println!("{} {}", hello, world);
// A string literal is already &str
let literal: &str = "Rust is fast";
println!("{}", literal);
}

Without slices, you might store an index into a string and then modify the string — leaving the index pointing to meaningless data. With a string slice, the borrow checker ensures the slice cannot outlive the data it references.

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[..i]; // slice up to the space
}
}
s // whole string is one word
}
fn main() {
let sentence = String::from("hello world");
let word = first_word(&sentence);
// sentence cannot be mutated while word (a slice of it) is alive
println!("first word: {}", word);
}

Functions that operate on sequences should accept slices rather than owned types. This lets them work with arrays, vectors, and any portion thereof without requiring ownership.

  • Prefer &[T] over &Vec<T> for sequence parameters
  • Prefer &str over &String for string parameters

Both &Vec<T> and &String coerce automatically to their slice equivalents at call sites.

fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[..i];
}
}
s
}
fn sum_slice(nums: &[i32]) -> i32 {
let mut total = 0;
for &n in nums {
total += n;
}
total
}
fn main() {
// String slice
let sentence = String::from("hello world");
let word = first_word(&sentence);
println!("first word: {}", word);
// Integer slice
let numbers = [10, 20, 30, 40, 50];
let middle: &[i32] = &numbers[1..4];
println!("middle slice: {:?}", middle);
println!("sum of middle: {}", sum_slice(middle));
// String slice literal
let greeting: &str = "Rust is safe";
println!("slice: {}", &greeting[..4]);
}
What type represents a borrowed view into part of a String?
What does the range syntax [1..4] select from a slice?
Does a slice own the data it points to?
Why should a function parameter use &[T] instead of &Vec<T>?