[Avg. reading time: 14 minutes]
Borrowing References
Borrowing allows you to use a value without taking ownership.
Rust enforces strict rules to prevent:
- Data races
- Dangling references
- Undefined behavior
These rules are checked at compile time by the borrow checker.
Borrowing Immutable References
An immutable reference allows read-only access.
// Cannot modify through immutable reference fn changeme(param_msg: &String) { // param_msg.push_str(" Green"); // ❌ not allowed println!("{param_msg}"); } fn main() { let msg = String::from("Rachel"); changeme(&msg); }
Why this fails if uncommented:
- &String is immutable
Example: Borrow a book from the library; you cannot write anything. Just read from it.
Borrowing Mutable References
A mutable reference allows read and write access.
// Mutable reference fn changeme(param_msg: &mut String) { param_msg.push_str(" Green") } fn main() { let mut msg = String::from("Rachel"); changeme(&mut msg); println!("{}", msg); }
Requirements:
- The original variable must be mut
- The reference must be &mut
Example: Borrow a book from a friend with permission to highlight or underline important items.
Immutable Borrow
- Allows read-only access to a value.
- Multiple immutable borrows are allowed.
- No mutation allowed through immutable reference.
// Immutable Borrow fn main() { let x = 5; // Immutable borrow let y = &x; *y += 1; println!("Value of y: {}", y); }
Mutable Borrow
Grants read-write access to a value. Only one mutable borrow is allowed at a time, and no immutable borrows can coexist.
// Mutable Borrow - Stack fn main() { let mut x = 5; println!("Value of x: {}", x); // Mutable borrow let z = &mut x; *z += 1; println!("Value of x: {}", x); }
Rule:
- You cannot have an active mutable borrow and immutable borrow at the same time.
- After the mutable borrow goes out of scope, immutable borrows are allowed.
That’s why they always use immutable borrows after mutable borrows.
fn main() { let mut x = 5; // Mutable borrow let z = &mut x; *z += 1; println!("Value of z: {}->{:p}", z, &z); println!("Value of x: {}->{:p}", x, &x); // immutable borrows let y1 = &x; println!("Value of y1: {}->{:p}", y1, &y1); // Flip the immutable and mutable and print the Immutable value after Mutable }
Use this website to sort the Memory locations to understand how values are stored in Stack. You can sort them by ASC or DESC order.
https://www.rajeshvu.com/storage/emc/utils/general/sorthexnumbers
Borrow Checker
// Borrow Checker - String - Heap fn main() { let mut a = String::from("Rachel"); let b = &mut a; println!("variable 'b' initial value is {} stored at this {:p}", b,b.as_ptr()); b.push_str(" Green"); //println!("variable 'a' {}{:p}", a,a.as_ptr()); println!("variable 'b' {} {:p}", b,b.as_ptr()); // println!("variable 'a' {}{:p}", a,a.as_ptr()); b.push_str(" !"); println!("variable 'b' after update {},{:p}", b,b.as_ptr()); println!("variable 'a' after update {} {:p}", a,a.as_ptr()); }
Why a cannot be used while b exists:
- b has exclusive access
Rust prevents simultaneous access to avoid data races
Example: The book owner can use the book only after the borrowed person has completed the work.
Multiple Mutable Borrowers (not allowed)
// Not allowed to have multiple Mutable Borrowers at the same time/scope fn main() { let mut a = String::from("Rachel"); let b = &mut a; let c = &mut a; println!("{}", b); println!("{}", c); }
Example: Two friends borrow the same book to highlight the important items.
// Multiple Mutable Borrowers (different scope) fn main() { let mut a = String::from("Rachel"); let b = &mut a; println!("{}", b); let c = &mut a; println!("{}", c); }
- Only one mutable reference exists at a time
- Scopes do not overlap
Example: 2 friends borrow the book one after another for writing.
For clarity’s sake, the above code can be written this way, too.
// Multiple Mutable Borrowers (different scope) fn main() { let mut a = String::from("Rachel"); { let b = &mut a; println!("{}", b); } let c = &mut a; println!("{}", c); }
Immutable and Mutable
Mutable first, followed by Immutable
// Immutable and Mutable fn main() { let mut a = String::from("Rachel"); let c = &mut a; c.push_str("!"); println!("c = {}", c); let b = &a; println!("b = {}", b);
Borrowing is what allows Rust to provide:
- Memory safety
- Data race prevention
- No garbage collector
- Zero runtime overhead
Rules:
- Multiple immutable references are allowed.
- Only one mutable reference is allowed at a time.
- Mutable and immutable references cannot coexist simultaneously.
- References must always be valid (no dangling references).