Rust Fundamentals: Ownership, Errors and Fearless Concurrency · Rust Basics and Ownership

Borrowing, References and Slices

Lend a value with & instead of moving it: any number of shared borrows, or exactly one mutable borrow at a time. Slices (&str, &[T]) borrow part of a collection. These rules prevent data races at compile time.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

Text tools that inspect and edit a document without taking ownership: count words, find the longest word, and append a signature, all using references and slices.

The idea

A reference lets a function use a value without owning it. Creating one is called borrowing:

  • &T: a shared (read-only) reference. You can have many at once.
  • &mut T: a mutable reference. You can have only one at a time, and no shared references while it exists.

These rules mean nobody can change data while someone else is reading it, which eliminates data races and iterator-invalidation bugs at compile time. References must also never outlive the value they point to (no dangling pointers).

A slice borrows a contiguous part of a collection:

  • &str is a string slice (string literals are &str), e.g. &text[0..5].
  • &[T] is a slice of an array or Vec.

Write functions that take &str and &[T] instead of &String and &Vec<T>: they then accept both owned values and slices.

The code

Shared borrows, one mutable borrow, and slices
fn word_count(text: &str) -> usize {
    text.split_whitespace().count()
}

fn longest_word(text: &str) -> &str {
    // returns a slice of the input: no copying
    let mut best = "";
    for word in text.split_whitespace() {
        let word = word.trim_matches(|c: char| !c.is_alphanumeric());
        if word.len() > best.len() {
            best = word;
        }
    }
    best
}

fn sign(doc: &mut String, name: &str) {
    doc.push_str("\n-- ");
    doc.push_str(name);
}

fn average(values: &[f64]) -> f64 {
    values.iter().sum::<f64>() / values.len() as f64
}

fn main() {
    let mut doc = String::from("Rust makes systems programming approachable and reliable.");

    let a = &doc;                      // many shared borrows are fine
    let b = &doc;
    println!("words: {}, longest: {}", word_count(a), longest_word(b));

    sign(&mut doc, "Ferris");          // one mutable borrow, after the shared ones are done
    println!("{doc}");

    let first_word = &doc[0..4];       // a string slice borrows part of the String
    println!("first word: {first_word}");

    let temps = vec![18.5, 21.0, 19.5, 23.0, 22.0];
    println!("weekday average: {:.2}", average(&temps[0..3])); // slice of a Vec
    println!("all-week average: {:.2}", average(&temps));

    // let r1 = &mut doc;
    // let r2 = &mut doc;              // error: cannot borrow `doc` as mutable more than once
    // println!("{r1} {r2}");
}

Output

words: 7, longest: approachable
Rust makes systems programming approachable and reliable.
-- Ferris
first word: Rust
weekday average: 19.67
all-week average: 20.80

Run this on your computer — see “Get Started” for setup.

doc is still owned by main at the end: every function only borrowed it.

Common mistakes

  • Mixing a mutable borrow with other borrows in overlapping scopes; finish reading before you write.
  • Returning a reference to a local variable (it would dangle); return an owned value instead.
  • Taking &String or &Vec<T> parameters. Take &str and &[T]: more flexible, same cost.
  • Slicing strings at non-character boundaries. &s[0..1] on "é" panics because UTF-8 characters can be several bytes; use .chars() for character work.

Key points

  • &T borrows read-only (many allowed); &mut T borrows for writing (only one, exclusive).
  • References can't outlive their data; the compiler checks this.
  • Slices &str and &[T] borrow part of a string, array or Vec.
  • Prefer &str / &[T] parameters.

Further reading: References and borrowing, The slice type.

Practice questions

State Rust's borrowing rules.

Answer

At any time you can have either any number of immutable references (&T) or exactly one mutable reference (&mut T) to a value, and references must always be valid (never outlive the value).

Why is `fn f(s: &str)` preferred over `fn f(s: &String)`?

Answer

&str accepts string literals, slices of Strings and &String (through deref coercion), so it's more flexible at no extra cost.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?