Ownership & Borrowing: Rust's Superpower 📚
This is the concept that makes Rust different. Once you get this, everything else in Rust makes sense. Skip this, and you'll be confused forever.
🎯 The Big Idea
Imagine a world where every piece of data has a clear owner, and that owner is responsible for cleaning up when done. That's Rust's ownership system.
📖 The Library Book Analogy
Think of data in Rust like books in a library:
Ownership = Buying the Book
#![allow(unused)] fn main() { let book = String::from("The Rust Book"); // You OWN this book give_away(book); // You gave it away // Can't use 'book' anymore - you don't own it! }
Borrowing = Library Loan
#![allow(unused)] fn main() { let book = String::from("The Rust Book"); // You own this read_book(&book); // Someone borrows it to read println!("{}", book); // You still have it! }
Mutable Borrowing = Loan with a Pencil
#![allow(unused)] fn main() { let mut book = String::from("The Rust Book"); // Mutable ownership add_notes(&mut book); // Loan it with permission to write println!("{}", book); // Got it back, with notes! }
🔑 The Three Rules (That's It!)
- Each value has exactly one owner
- You can have EITHER:
- One mutable borrow (
&mut T) - OR any number of immutable borrows (
&T) - But not both at the same time!
- One mutable borrow (
- When the owner goes out of scope, the value is dropped
👀 Quick Recognition Guide
| What You See | What It Means | Can You Still Use Original? |
|---|---|---|
foo(x) | Moving ownership | ❌ No, it's gone |
foo(&x) | Immutable borrow | ✅ Yes, unchanged |
foo(&mut x) | Mutable borrow | ✅ Yes, might be changed |
let y = x | Move ownership to y | ❌ No, y owns it now |
let y = &x | y borrows from x | ✅ Yes, both valid |
let y = x.clone() | Make a copy | ✅ Yes, independent copy |
🎨 Visual Examples
Example 1: Moving Ownership
fn main() { let s1 = String::from("hello"); // s1 owns "hello" let s2 = s1; // Ownership MOVES to s2 // println!("{}", s1); // ❌ ERROR! s1 no longer owns it println!("{}", s2); // ✅ s2 owns it now }
Think of it as: Handing someone your car keys. You can't drive it anymore!
Example 2: Borrowing
fn calculate_length(s: &String) -> usize { // Borrows, doesn't own s.len() // Can read it // s.push_str("more"); // ❌ Can't modify! } fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // Lend it temporarily println!("Length of '{}' is {}", s1, len); // ✅ Still have it! }
Think of it as: Letting someone look at your phone to check the time.
Example 3: Mutable Borrowing
fn add_world(s: &mut String) { s.push_str(", world"); // Can modify because it's &mut } fn main() { let mut s = String::from("Hello"); // Note: mut is required add_world(&mut s); // Lend with edit permissions println!("{}", s); // Prints: "Hello, world" }
Think of it as: Letting someone borrow your notebook to add their notes.
🚩 Red Flags & Common Patterns
The "Use After Move" Error
#![allow(unused)] fn main() { let data = vec![1, 2, 3]; process_data(data); // Moved! println!("{:?}", data); // ❌ ERROR: value used after move }
Fix: Either clone or borrow:
#![allow(unused)] fn main() { process_data(data.clone()); // Send a copy // OR process_data(&data); // Lend it instead }
The "Cannot Borrow as Mutable" Error
#![allow(unused)] fn main() { let x = 5; // Not mutable change_value(&mut x); // ❌ ERROR: cannot borrow as mutable }
Fix: Declare as mutable:
#![allow(unused)] fn main() { let mut x = 5; // Now it's mutable change_value(&mut x); // ✅ Works! }
The Iterator Pattern
#![allow(unused)] fn main() { let numbers = vec![1, 2, 3]; numbers.iter() // Borrows each element .map(|n| n * 2) // Process borrowed values .collect(); // Create new collection // numbers still available here! }
🎯 What to Look For When Reading Code
1. Function Signatures Tell You Everything
#![allow(unused)] fn main() { fn take_ownership(s: String) { } // Will consume the input fn borrow(s: &String) { } // Just wants to read fn borrow_mut(s: &mut String) { } // Wants to modify fn return_ownership() -> String { } // Gives you ownership }
2. The .clone() Escape Hatch
When you see .clone(), someone is making a copy to avoid ownership issues:
#![allow(unused)] fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // Explicit copy // Both s1 and s2 are usable }
3. Common Borrowing Patterns
#![allow(unused)] fn main() { // Iteration borrows by default for item in &collection { } // Borrow each item for item in &mut collection { } // Mutably borrow each item for item in collection { } // Take ownership (consumes collection) // Method chains often borrow text.trim().to_lowercase() // Borrows text, returns new String }
💡 Mental Shortcuts
When Reading Rust Code, Ask:
- Who owns this data? (Look for the original
letbinding) - Is it being moved or borrowed? (Look for
&) - Can it be modified? (Look for
mut)
Quick Rules:
- No
&= Moving (ownership transfer) &withoutmut= Reading (immutable borrow)&mut= Modifying (mutable borrow).clone()= Copying (avoiding ownership complexity)
🎮 Interactive Example
Try to predict what happens:
fn main() { let mut x = 5; // x owns the value 5 let y = &x; // y borrows x let z = &x; // z also borrows x (multiple readers OK!) // let w = &mut x; // ❌ ERROR! Can't mutably borrow while borrowed println!("{} {}", y, z); // Use the borrows let w = &mut x; // ✅ NOW we can mutably borrow (y and z done) *w += 1; // Modify through the mutable borrow println!("{}", x); // Prints: 6 }
🏁 Summary
- Ownership = Who's responsible for cleaning up
- Borrowing = Temporary access without ownership
&= "I just want to look"&mut= "I need to change this"- No
&= "I'm taking this"
The compiler enforces these rules at compile time, preventing entire classes of bugs that plague other languages.
✅ Quick Check
If you understand why this doesn't work:
#![allow(unused)] fn main() { let s = String::from("hello"); let r1 = &s; let r2 = &mut s; // ❌ Can't have & and &mut at same time }
But this does:
#![allow(unused)] fn main() { let mut s = String::from("hello"); { let r1 = &s; println!("{}", r1); } // r1 goes out of scope let r2 = &mut s; // ✅ Now we can mutably borrow }
Then you understand ownership and borrowing!
Next: Types & Memory - Now that you understand ownership, let's see how Rust organizes data →