RustArena

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.

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

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

Practice it