Phase 2Ownership & borrowing

#7 Slices

&str, &[T], string slices

What is a slice?

A slice is a reference to a contiguous sequence of elements within a collection. Slices let you reference a portion of data without copying it and without taking ownership.

Slices are one of the most common types in Rust. They are written as &[T] for arrays and &str for strings.

String slices (&str)

A string slice (&str) is a reference to a part of a String. You create one using range syntax:

String slices
let s = String::from("Hello, world!");

let hello = &s[0..5];    // "Hello"
let world = &s[7..12];   // "world"

// Shorthand: omit start or end
let hello = &s[..5];     // Same as &s[0..5]
let world = &s[7..];     // Same as &s[7..13]
let full = &s[..];       // The whole string

println!("First word: {hello}");

String literals like "hello" are already &str — they are slices pointing to a specific point in the binary. That is why the type of a string literal is &str, not String.

first_word function
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  // No space found — return the whole string
}

let sentence = String::from("Hello world");
let word = first_word(&sentence);
println!("First word: {word}");  // "Hello"

Array slices (&[T])

Array slices work the same way. They reference a portion of an array or vector:

Array slices
let numbers = [1, 2, 3, 4, 5];

let slice = &numbers[1..4];  // [2, 3, 4]
println!("Slice: {:?}", slice);

let first_two = &numbers[..2];  // [1, 2]
let last_two = &numbers[3..];   // [4, 5]
let all = &numbers[..];         // [1, 2, 3, 4, 5]

The type &[i32] is a slice of i32 values. It stores a pointer to the first element and a length.

Slices as function parameters

Using slices as function parameters makes your functions more flexible. A function that takes &str can accept both String and &str values:

Slices in functions
fn sum(numbers: &[i32]) -> i32 {
    let mut total = 0;
    for &n in numbers {
        total += n;
    }
    total
}

fn greet(name: &str) -> String {
    format!("Hello, {name}!")
}

fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    println!("Sum: {}", sum(&nums));

    // Works with arrays too
    let arr = [10, 20, 30];
    println!("Sum: {}", sum(&arr));

    // &str works with both String and &str
    let name = String::from("world");
    println!("Greeting: {}", greet(&name));
    println!("Greeting: {}", greet("Rust"));
}

Tip: prefer &str over &String and &[T] over &Vec<T> in function signatures. This makes your functions more generic and idiomatic.

Your turn

Try running the slices demo with cargo run and cargo check in the terminal below:

terminal — cargo
user@stemlegacy:~/slices-demo$