Rust Borrowing
Using references without taking ownership
🔗 What is Borrowing?
Borrowing allows you to use values without taking ownership through references. You can have multiple immutable references or one mutable reference at a time, ensuring memory safety and preventing data races at compile time.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // Borrow s1
println!("The length of '{}' is {}.", s1, len); // s1 still valid
}
fn calculate_length(s: &String) -> usize {
s.len() // s is a reference, doesn't own the data
}
Output:
The length of 'hello' is 5.
Types of Borrowing
Immutable Reference
Read-only access to data
let r = &s // immutable reference
Mutable Reference
Read and write access to data
let r = &mut s; // mutable reference
Multiple Immutable
Many readers allowed simultaneously
let r1 = &s let r2 = &s
Single Mutable
Only one writer at a time
let r = &mut s; // exclusive access
🔹 Immutable References
Create references that allow reading but not modifying:
fn main() {
let s = String::from("hello world");
// Multiple immutable references are allowed
let r1 = &s
let r2 = &s
let r3 = &s
println!("r1: {}", r1);
println!("r2: {}", r2);
println!("r3: {}", r3);
// Original variable still accessible
println!("Original: {}", s);
// All references point to the same data
println!("All equal: {}", r1 == r2 && r2 == r3);
}
Output:
r1: hello world
r2: hello world
r3: hello world
Original: hello world
All equal: true
🔹 Mutable References
Create references that allow both reading and modifying:
fn main() {
let mut s = String::from("hello");
// Create a mutable reference
let r1 = &mut s;
r1.push_str(", world");
println!("{}", r1);
// After r1 is done, we can create another mutable reference
let r2 = &mut s;
r2.push('!');
println!("{}", r2);
// Or use the original variable
println!("Final: {}", s);
}
Output:
hello, world
hello, world!
Final: hello, world!
🔹 Borrowing Rules
Rust enforces strict borrowing rules to prevent data races:
fn main() {
let mut s = String::from("hello");
// ✅ This works: multiple immutable references
let r1 = &s
let r2 = &s
println!("{} and {}", r1, r2);
// r1 and r2 are no longer used after this point
// ✅ This works: one mutable reference after immutable ones are done
let r3 = &mut s;
r3.push_str(" world");
println!("{}", r3);
// ❌ This would NOT work: mixing mutable and immutable references
// let r4 = &s // immutable borrow
// let r5 = &mut s; // mutable borrow - ERROR!
// println!("{} {}", r4, r5);
}
Output:
hello and hello
hello world
🔹 Functions with References
Pass references to functions to avoid taking ownership:
fn main() {
let mut s = String::from("hello");
// Pass immutable reference
let len = get_length(&s);
println!("Length: {}", len);
// Pass mutable reference
add_world(&mut s);
println!("Modified: {}", s);
// Original variable still owned and usable
println!("Still accessible: {}", s);
}
fn get_length(s: &String) -> usize {
s.len() // Can read but not modify
}
fn add_world(s: &mut String) {
s.push_str(" world"); // Can modify the string
}
Output:
Length: 5
Modified: hello world
Still accessible: hello world
🔹 Dangling References Prevention
Rust prevents dangling references at compile time:
fn main() {
// ✅ This works: reference lives as long as the data
let s = String::from("hello");
let r = &s
println!("{}", r);
// ❌ This would NOT compile: dangling reference
// let reference_to_nothing = dangle();
}
// This function would create a dangling reference
// fn dangle() -> &String { // ERROR: missing lifetime specifier
// let s = String::from("hello");
// &s // s goes out of scope, but we're returning a reference to it
// }
// ✅ Correct way: return the owned value
fn no_dangle() -> String {
let s = String::from("hello");
s // Return ownership, no reference needed
}
Output:
hello