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.
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:
&stris a string slice (string literals are&str), e.g.&text[0..5].&[T]is a slice of an array orVec.
Write functions that take &str and &[T] instead of &String and &Vec<T>: they then accept both owned values and slices.
The code
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
&Stringor&Vec<T>parameters. Take&strand&[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
&Tborrows read-only (many allowed);&mut Tborrows for writing (only one, exclusive).- References can't outlive their data; the compiler checks this.
- Slices
&strand&[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.