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.

Borrowing Types

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
1️⃣

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

🧠 Test Your Knowledge

How many mutable references can you have to the same data at once?