Build the mental model
Ownership is Rust's core concept, defined by just three rules: each value has exactly one owning variable, only one owner exists at a time, and when the owner goes out of scope, the value is automatically dropped (memory freed). When heap-allocated data (like `String::from("hello")`) is assigned from one variable to another (`let s2 = s1;`), Rust does not deep-copy by default — it moves ownership, so trying to keep using `s1` afterward triggers a "value borrowed after move" compile error; this is the mechanism that prevents bugs like double-frees at compile time instead of at runtime. Move-by-default can feel strange at first, but the key insight is that because a value always has exactly one owner, there is never a moment where two variables compete to deallocate the same memory when it goes out of scope — so Rust needs no garbage collector at all, tracking ownership entirely at compile time and giving deterministic, scope-exit-based memory management with predictable timing. Think of it like handing over a physical key: once you hand it to someone else, you no longer have it yourself — if you actually want a duplicate, you have to ask for one explicitly with `s1.clone()`.
Connect it to a real scenario
If the content analyzer's file-reading function is written as `fn process(content: String) { ... }`, taking ownership, the caller cannot keep using `content` after calling it — ownership has moved. Avoiding that by borrowing instead of moving is exactly what Lesson 7's `&content` (a reference) is for, making it a direct follow-up to this one. When collecting word count results into a `Vec<(String, u32)>`, pushing a `file_name` string inside a loop moves ownership each time, so think about whether the loop variable needs cloning to be used again afterward — designing the algorithm to be move-friendly and avoiding unnecessary clones is central to writing performance-conscious Rust.
Try the working example
fn main() {
let s1 = String::from("ownership");
let s2 = s1; // s1's value moves into s2
// println!("{s1}"); // would not compile: value borrowed after move
println!("{s2}");
let s3 = s2.clone(); // explicit deep copy
println!("{s2} and {s3}");
}You print "ownership", then "ownership and ownership" — uncommenting the s1 line demonstrates the compile error.5-minute try-it
Pass a `String` variable to a function by moving ownership (parameter type `String`) — after calling the function, try using the original variable and read the compile error — then fix it with `.clone()`.
One important caution
Defaulting a function parameter's type to `String` (owned) and not expecting the caller to lose ownership, then trying to keep using that variable — resulting in a "value borrowed after move" error.
Reaching for `.clone()` everywhere as a default habit to avoid moves — every clone deep-copies heap data and has a real performance cost; learn borrowing (Lesson 7) and reserve clone for where it's actually needed.