Build the mental model
To avoid moving ownership as in Lesson 6, you can "borrow" a value with a reference (`&`) — passing `&content` to a function leaves ownership with the caller, and the function only gets temporary permission to look at the value; the borrow ends when the function returns. References are immutable (`&T`) by default; to change the value, you must explicitly request a mutable reference (`&mut T`). The borrow checker's core rule is that, in the same scope, a value can have either exactly one mutable reference or any number of immutable references — never both at once — which prevents read-write conflicts (one place reading data while another mutates it) from ever occurring, at compile time. Think of it like a library book: no one else can read it while someone is editing it in pencil (data could get corrupted), but as long as no one is editing, many people can safely read it at once. Enforcing this rule at compile time is what extends it across threads in the concurrency lesson (Lesson 18) too, turning a data race into a compiler error rather than a runtime bug.
Connect it to a real scenario
Write the content analyzer's `count_words` as `fn count_words(text: &str) -> u32`, borrowing rather than owning, so the caller's original `String` remains usable after calling it. To update the lesson stats vector in place, pass a mutable reference: `fn update_stats(stats: &mut Vec<LessonStat>) { ... }` — while it's active, no other reference (mutable or immutable) to that vector can be alive at the same time, and the compiler enforces this automatically. For report-generation functions that only need to read the files array, consistently use `&Vec<LessonStat>` (an immutable borrow) so multiple functions can read it concurrently.
Try the working example
fn count_words(text: &str) -> u32 {
text.split_whitespace().count() as u32
}
fn append_tag(tags: &mut Vec<String>, tag: &str) {
tags.push(tag.to_string());
}
fn main() {
let content = String::from("Rust references and borrowing");
let count = count_words(&content);
println!("{content} -> {count} words"); // content still usable
let mut tags = vec![String::from("rust")];
append_tag(&mut tags, "ownership");
println!("{tags:?}");
}You print "Rust references and borrowing -> 4 words" and `["rust", "ownership"]`.5-minute try-it
Create a `Vec<String>` variable and take two immutable references (`&v`) to it at the same time (allowed) — then, while one immutable reference is still active, try taking a mutable reference (`&mut v`) too — read the resulting compile error.
One important caution
Trying to take a mutable and an immutable reference to the same value in the same scope at once — the borrow checker immediately blocks it with "cannot borrow as mutable because it is also borrowed as immutable."
Writing a function's parameter as owned (`String`) when a borrow (`&str`) would suffice, forcing every caller into an unnecessary ownership move — designing borrow-first gives callers more flexibility.
The Rust Programming Language — References and Borrowing — Rust