Slices and string slices

Rust · Essentials · 10 min

Taking slices instead of owned collections makes your functions both cheaper and more flexible.

A slice is a borrowed view into a contiguous sequence: a pointer plus a length. &[T] views elements of an array or Vec<T>, and &str views UTF-8 bytes of a String or string literal. Slices do not own anything, so they follow the borrowing rules.

Create them with range syntax: &v[1..3] (elements 1 and 2), &v[..2], &v[2..], &v[..]. Out-of-range bounds panic.

String literals like "hello" have type &'static str: a slice pointing into the program binary, valid for the whole run.

The most useful habit is in function signatures. Prefer:

  • &str over &String,
  • &[T] over &Vec<T>.

Thanks to deref coercion, a &String automatically converts to &str and a &Vec<T> to &[T], so the slice version accepts strictly more callers, including literals and arrays.

Because a slice borrows from its source, the compiler keeps the source alive and unmodified while the slice is in use. A classic example: if a function returns &str pointing at the first word of a String, you cannot clear() the string while still using that word.

One gotcha: string slice indices are byte offsets. Slicing in the middle of a multi-byte UTF-8 character, like &"héllo"[0..2], panics at runtime. When working with text, prefer methods such as split_whitespace, find or char_indices that return valid boundaries.

&mut [T] is the mutable counterpart, letting you modify elements in place (for example slice.sort()), though it cannot change the length.

Example

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}

fn sum(xs: &[i32]) -> i32 {
    xs.iter().sum()
}

fn main() {
    let owned = String::from("hello world");
    println!("{}", first_word(&owned));       // hello  (&String -> &str)
    println!("{}", first_word("one-word"));   // one-word (literal is &str)

    let v = vec![1, 2, 3, 4];
    let arr = [10, 20];
    println!("{} {}", sum(&v[1..3]), sum(&arr)); // 5 30

    let mut nums = [3, 1, 2];
    nums[..].sort();                            // &mut [i32]
    println!("{nums:?}");                        // [1, 2, 3]
}

Remember

  • A slice is a borrowed pointer + length: &[T] or &str
  • Accept &str and &[T] in signatures; &String and &Vec coerce to them
  • String slice indices are byte offsets and must land on char boundaries

Watch out for

  • Slicing text by byte index and panicking on non-ASCII characters.

Check yourself

  1. Which parameter type accepts a `String`, a string literal and a `&str` from elsewhere?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: doc.rust-lang.org/book/ch04-03-slices.html