Slices in Rust: &str, &[T] and ranges
What slices are in Rust, how to take &s[a..b] and &v[a..b], why &str and &[T] make better parameters, and the panics to avoid.
A slice is a reference to a contiguous part of a collection: a view into some elements, without owning them. It's stored as a pointer plus a length (a fat pointer), so taking a slice never copies data.
&stris a slice of UTF-8 text, from aStringor a literal.&[T]is a slice of elements, from an array, aVec<T>, or another slice.&mut [T]is a mutable slice: you can change elements, but not add or remove them.
Range syntax
| Range | Means |
|---|---|
&v[1..4] |
indexes 1, 2, 3 (end is excluded) |
&v[1..=4] |
indexes 1 to 4 inclusive |
&v[..3] |
from the start to index 2 |
&v[2..] |
from index 2 to the end |
&v[..] |
everything |
fn main() {
let temps = [18, 21, 25, 23, 19];
let weekend = &temps[3..];
println!("{weekend:?} has {} days", weekend.len());
let line = String::from("WARN low disk");
let level = &line[..4];
println!("level={level}");
}
[23, 19] has 2 days
level=WARN
Slices make better parameters
A function taking &[T] accepts arrays, vectors and parts of either; one taking &str accepts literals, Strings and substrings. &Vec<T> or &String would accept only the owned type, so prefer slices for anything you only read. (Clippy warns about &Vec<T> and &String parameters with ptr_arg.) A &Vec<T> or &String converts to a slice automatically when you pass it.
fn average(values: &[f64]) -> f64 { ... }
average(&readings); // a whole Vec
average(&readings[..7]); // the first week
average(&[1.0, 2.0]); // an array
Because slices borrow, the borrowing rules apply: while a slice of a Vec is in use, you can't push to the Vec (error E0502). That's exactly what keeps the slice from dangling when the Vec reallocates.
Useful slice methods
len, is_empty, first, last, get(i) (returns an Option), contains, iter, split_at(mid), chunks(n), windows(n), sort and reverse (on &mut [T]), binary_search, starts_with. For strings: find, split, trim, starts_with, lines.
Common mistakes
- Out-of-range slices panic.
&v[2..10]on a 3-element array panics withrange end index 10 out of range for slice of length 3. Usev.get(2..10), which returnsNoneinstead. - Slicing text in the middle of a character. String ranges are in bytes, and many characters take more than one byte in UTF-8.
&"héllo"[0..2]panics:end byte index 2 is not a char boundary; it is inside 'é' (bytes 1..3 of string). Usechar_indices(),find, orchars()to get safe positions. - Returning a slice of a local
String. TheStringis freed at the end of the function, so the compiler refuses (E0515 or E0106). Return aString, or return a slice of the input parameter, likefirst_wordbelow.
Example
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[..i],
None => text,
}
}
fn average(values: &[f64]) -> f64 {
let mut sum = 0.0;
for v in values {
sum += v;
}
sum / values.len() as f64
}
fn main() {
let line = String::from("ERROR disk full on /dev/sda1");
println!("level: {}", first_word(&line));
println!("literal: {}", first_word("hello"));
let temps = vec![21.5, 23.0, 19.5, 22.0, 24.5];
println!("all: {:.2}", average(&temps));
println!("last three: {:.2}", average(&temps[2..]));
let mut scores = [40, 10, 30, 20];
scores[1..].sort();
println!("{scores:?}");
let (left, right) = scores.split_at(2);
println!("{left:?} {right:?}");
for pair in temps.windows(2) {
print!("{:+.1} ", pair[1] - pair[0]);
}
println!();
println!("{:?} {:?}", temps.get(1..3), temps.get(3..10));
}
Output:
level: ERROR
literal: hello
all: 22.10
last three: 22.00
[40, 10, 20, 30]
[40, 10] [20, 30]
+1.5 -3.5 +2.5 +2.5
Some([23.0, 19.5]) None