Rust by Example: Borrowing and References
Learn how to use data without taking ownership of it. This guide explains immutable and mutable references, the rules of borrowing, and how Rust ensures memory safety through compile-time checks on reference validity.
Code
fn main() {
let s1 = String::from("hello");
// Pass a reference (&s1) instead of moving ownership
let len = calculate_length(&s1);
// s1 is still valid here because we only "borrowed" it
println!("The length of '{}' is {}.", s1, len);
// --- Mutable References ---
let mut s2 = String::from("hello");
change(&mut s2);
println!("Changed string: {}", s2);
}
fn calculate_length(s: &String) -> usize {
// s is a reference to a String
s.len()
// s goes out of scope, but because it doesn't have ownership,
// nothing happens (the string is not dropped)
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}Explanation
Moving ownership every time we want to use a variable would be tedious. Rust solves this with References, also known as Borrowing. A reference allows you to refer to some value without taking ownership of it. Under the hood, a reference is just a pointer (usually 8 bytes on 64-bit systems) that points to the memory location of the original data.
References are created using the ampersand & symbol. &s1 creates a reference to the value of s1. Because the function calculate_length only borrows s1, the original variable remains valid after the function call returns. By default, references are immutable; you cannot modify the borrowed value.
If you need to modify the borrowed value, you use a Mutable Reference (&mut). However, Rust enforces a strict rule to prevent data races, known as "Aliasing XOR Mutability":
- You can have either one mutable reference...
- ...OR any number of immutable references.
- But NOT both at the same time.
Code Breakdown
calculate_length(&s1). The & creates a reference. We are passing a pointer to the data, not the data itself. This means s1 retains ownership.change(&mut s2). We create a mutable reference using &mut. The variable s2 must also be declared as mut for this to work.fn calculate_length(s: &String). The function signature explicitly states that it expects a reference to a String (&String), not the String itself.
