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.
