Chapter 21: Rust Borrowing
Rust Borrowing like we’re sitting together with a laptop and some filter coffee: slowly, clearly, with many small examples you can copy-paste and run, everyday analogies from Hyderabad life, and building from very simple → common patterns → tricky cases.
1. What is Borrowing? (The Simple Idea)
Borrowing = temporarily accessing data without taking ownership of it.
You “borrow” the data for a while, use it (read or write), then give it back — the original owner still owns it after you’re done.
Rust has two kinds of borrowing:
| Kind | Syntax | Can read? | Can write/mutate? | How many at the same time? | Analogy (Hyderabad style) |
|---|---|---|---|---|---|
| Immutable borrow | &T | Yes | No | Many (unlimited) | Many people take photos of your biryani plate |
| Mutable borrow | &mut T | Yes | Yes | Exactly one at a time | Only one friend is allowed to eat from your plate |
These two rules are enforced at compile time by the borrow checker — no runtime cost.
2. Immutable Borrowing (&) – Read-Only Access
Most common borrowing — safe, cheap, unlimited.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
fn main() { let s = String::from("Namaste Hyderabad"); let len = calculate_length(&s); // borrow immutable let first_word = first_word(&s); // another borrow — OK! println!("String still exists: '{}'", s); // owner still has it println!("Length: {}, First word: {}", len, first_word); } fn calculate_length(s: &String) -> usize { s.len() // read only } fn first_word(s: &str) -> &str { // &str is very common let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } s // whole string if no space } |
Key points:
- &s = create an immutable reference (borrow)
- Function gets a view — does not own or move anything
- You can have as many immutable borrows as you want at the same time
- Original owner (s) still fully owns the data — can keep using it
3. Mutable Borrowing (&mut) – Exclusive Write Access
Only one mutable borrow allowed at any moment.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
fn main() { let mut s = String::from("Hello"); change(&mut s); // give mutable borrow println!("After change: {}", s); // still own it } fn change(s: &mut String) { s.push_str(", world!"); // mutate through &mut } |
But this fails:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
fn main() { let mut s = String::from("Rust"); let r1 = &mut s; // first mutable borrow let r2 = &mut s; // second mutable borrow → ERROR! // println!("{}, {}", r1, r2); } |
Compiler error (very clear in 2026):
|
0 1 2 3 4 5 6 |
error[E0499]: cannot borrow `s` as mutable more than once at a time |
→ Prevents data races even in single-threaded code
4. The Famous Borrowing Rules (Official + Explained)
Rust enforces these at the same time:
- At any given time, you can have either:
- Any number of immutable borrows (&T)
- OR exactly one mutable borrow (&mut T)
- References must always be valid (cannot outlive the data they point to → dangling prevented)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
fn main() { let mut s = String::from("Telangana"); let r_imm = &s; // immutable borrow let r_imm2 = &s; // another — OK // let r_mut = &mut s; // ERROR! cannot borrow mutably while immutables exist println!("{}, {}", r_imm, r_imm2); // use them let r_mut = &mut s; // now OK — previous immutables no longer used r_mut.push_str(" is great!"); } |
→ The borrow checker tracks until last use, not until end of scope
5. Common Pattern: &str vs &String in Functions
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
fn print_greeting(greeting: &str) { // prefer &str println!("Greeting: {}", greeting); } fn main() { let literal = "Namaste"; // &'static str let owned = String::from("Hello"); print_greeting(literal); // OK print_greeting(&owned); // &String coerces to &str print_greeting(&owned[..]); // explicit slice print_greeting(owned.as_str()); // explicit } |
→ Always prefer &str in function parameters — more flexible, zero-cost coercion from &String
6. Dangling References – Prevented at Compile Time
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
fn main() { let r; // declare { let s = String::from("short"); r = &s; // borrow } // s dropped here // println!("{}", r); // ERROR! r would dangle } |
Error: “borrowed value does not live long enough”
7. Borrowing in Structs & Methods (Quick Preview)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct Book { title: String, } impl Book { fn summary(&self) -> &str { // &self = immutable borrow &self.title } fn change_title(&mut self, new: &str) { self.title = new.to_string(); } } |
Teacher Summary – The Big Picture
Rust Borrowing =
- Temporarily access data without moving ownership
- &T → read-only, many allowed simultaneously
- &mut T → read-write, only one allowed at a time
- Prevents data races, use-after-free, dangling pointers — all at compile time
- Key to writing safe, efficient code without GC or manual memory management
Analogy for Hyderabad: Your family owns one scooter (the String).
- Immutable borrow = many friends take selfies with the scooter (everyone can look)
- Mutable borrow = only one friend is allowed to ride it (exclusive control)
- You can’t let someone ride it while others are taking photos at the same time — Rust stops you before accident happens.
Practice today:
- Write function that borrows &str and returns length + first 5 chars
- Create mutable borrow conflict → read compiler message
- Try to mutate through &String (should fail)
- Use &mut to append city name to a greeting
Ready for the next big piece?
- Lifetimes (they explain how long borrows are allowed to live)?
- Borrow checker error messages in detail?
- Or structs + borrowing inside methods?
Just tell me — your Rust teacher is right here with you! 🦀🚀
