Skip to content
AgileLogics
6 min read

Slices

Prerequisites: References & borrowing

A slice is a reference to a contiguous part of a collection, rather than the whole thing. Slices don't own data; they borrow it.

Syntax

let s = String::from("hello world");
let hello: &str = &s[0..5];   // string slice
let world: &str = &s[6..];    // from index 6 to the end

let numbers = [10, 20, 30, 40];
let middle: &[i32] = &numbers[1..3]; // array slice: [20, 30]

Range forms work the same way for strings and arrays:

  • a..b — from a up to, but not including, b
  • a.. — from a to the end
  • ..b — from the start up to b
  • .. — the whole thing

String slices (&str). A string literal like "hi" is itself a &str — a slice pointing into the program's binary. String slice indexes are byte positions, not character positions.

Why &str makes better parameters. A function that takes &str accepts both string literals and &String values — Rust converts &String to &str automatically. The same goes for &[T]: a function taking &[i32] accepts a reference to an array or (later) a vector.

Example

fn first_word(text: &str) -> &str {
    for (i, byte) in text.bytes().enumerate() {
        if byte == b' ' {
            return &text[..i];
        }
    }
    text
}

fn sum(values: &[i32]) -> i32 {
    let mut total = 0;
    for v in values {
        total += v;
    }
    total
}

fn main() {
    let sentence = String::from("hello world");
    let hello = &sentence[0..5];
    let world = &sentence[6..];
    println!("{hello} | {world}");

    println!("first word: {}", first_word(&sentence));
    println!("first word: {}", first_word("rust is fun"));

    let numbers = [10, 20, 30, 40, 50];
    let middle = &numbers[1..4];
    println!("{middle:?}");
    println!("len {}, sum {}", middle.len(), sum(middle));
    println!("sum of all: {}", sum(&numbers));
}

text.bytes() goes through the string byte by byte, and b' ' is the byte value of a space.

Expected output

hello | world
first word: hello
first word: rust
[20, 30, 40]
len 3, sum 90
sum of all: 150

Common mistakes

  • Changing the data while a slice borrows it:
let mut s = String::from("hello world");
let word = &s[..5];
s.clear();
println!("{word}");
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable

This is the borrowing rules protecting you: word would point to text that no longer exists.

  • Slicing past the end. &s[0..20] on an 11-byte string compiles, but panics at runtime with a message reporting that byte index 20 is past the end of the string.
  • Writing &String or &[i32; 5] parameters where &str or &[i32] would do. The slice types accept more kinds of arguments.

Practice

Write a function largest(values: &[i32]) -> i32 that returns the biggest number in a (non-empty) slice. Call it with the array [3, 9, 2, 7] and print Largest: 9.

Show solution
fn largest(values: &[i32]) -> i32 {
    let mut max = values[0];
    for &v in values { // `&v` copies each i32 out of its reference
        if v > max {
            max = v;
        }
    }
    max
}

fn main() {
    let numbers = [3, 9, 2, 7];
    println!("Largest: {}", largest(&numbers));
}