Rust Borrowing and References

In the previous topic you learned that passing a value into a function moves ownership, making the original variable unusable. Borrowing solves this problem. It lets you give a function access to a value without giving up ownership.

What Is a Reference?

A reference points to a value without owning it. Think of it as a visitor's pass — you can look at and use the value, but you do not own it and cannot take it with you when you leave.

Create a reference using the ampersand & symbol:

fn print_length(s: &String) {
    println!("Length: {}", s.len());
}

fn main() {
    let my_string = String::from("hello");
    print_length(&my_string);              ← Pass a reference
    println!("{}", my_string);            ← my_string still works
}

Output:

Length: 5
hello

The function receives a reference to my_string, not ownership. When the function ends, the reference disappears but my_string stays intact in main.

The Library Visitor Diagram

let my_string = String::from("hello");   ← You own the book

print_length(&my_string);
             ↑
     You hand the librarian a pass to READ the book.
     The librarian reads it and returns the pass.
     You still own the book afterward.

Immutable References

By default, references are immutable. A function that receives an immutable reference can read the value but cannot change it:

fn try_to_change(s: &String) {
    s.push_str(" world");   ← Compiler error: cannot modify borrowed value
}

This keeps your data safe. You can hand out multiple immutable references at the same time without any conflict — everyone reads the same value and no one changes it.

Multiple Immutable References

let s = String::from("hello");

let r1 = &s;
let r2 = &s;
let r3 = &s;

println!("{}, {}, {}", r1, r2, r3);  ← All three work fine

Mutable References

To allow a function to modify a borrowed value, use a mutable reference &mut:

fn add_world(s: &mut String) {
    s.push_str(" world");
}

fn main() {
    let mut my_string = String::from("hello");
    add_world(&mut my_string);
    println!("{}", my_string);
}

Output:

hello world

Notice two things: the variable must be declared with mut, and the call must pass &mut.

The Borrowing Rules

Rust enforces strict rules about references. Breaking these rules causes a compile error.

Rule 1: One Mutable Reference at a Time

At any point in time, you can have either one mutable reference or any number of immutable references — never both at the same time.

let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;   ← Compiler error: cannot borrow `s` as mutable twice

println!("{}, {}", r1, r2);

Rule 2: No Mixing Mutable and Immutable

let mut s = String::from("hello");

let r1 = &s;       ← Immutable reference
let r2 = &s;       ← Another immutable reference — fine
let r3 = &mut s;   ← Compiler error: mutable reference while immutables exist

println!("{}, {}, {}", r1, r2, r3);

Why These Rules Exist

If one part of your program changes a value while another part is reading it, the reader can get garbage data. This is called a data race. The borrowing rules make data races impossible — the compiler prevents code with data races from building at all.

The Single Editor Diagram

Immutable borrows (readers):
  r1 reads s ──┐
  r2 reads s ──┤  All allowed at the same time
  r3 reads s ──┘

Mutable borrow (editor):
  r_edit modifies s   ← Only ONE allowed, and NO readers at the same time

Rule: You can have many readers OR one editor, never both.

Reference Scopes and Non-Lexical Lifetimes

A reference's scope ends at the last place it is used, not at the closing brace of its block. This means you can create a mutable reference after immutable ones, as long as the immutable ones are no longer in use:

let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);  ← r1 and r2 last used here, their scope ends

let r3 = &mut s;             ← OK: r1 and r2 are no longer active
println!("{}", r3);

Dangling References

A dangling reference points to memory that has already been freed. In C, this causes crashes. Rust makes dangling references impossible. The compiler rejects any code where a reference could outlive the data it points to:

fn dangle() -> &String {      ← Compiler error: returns a reference to
    let s = String::from("hello");  ← dropped value
    &s
}   // s is dropped here. The reference would point to freed memory.

The fix is to return the String itself (transferring ownership) rather than a reference to it.

Summary of Reference Rules

&T             ← Immutable reference (many allowed at once)
&mut T         ← Mutable reference (only one at a time)

At any given moment, you can have:
  Many &T references           OR
  Exactly one &mut T reference
  (never both at the same time)

References must always be valid — no dangling references allowed.

Leave a Comment