Rust Borrowing
Introduction
In the previous section, we explored Rust's ownership system - a fundamental concept that helps Rust ensure memory safety without a garbage collector. While ownership provides strong guarantees about memory management, it would be quite limiting if it meant that variables could only ever be used in one place. This is where borrowing comes in.
Borrowing is Rust's way of allowing multiple parts of your code to access data without transferring ownership. Think of it like borrowing a book from a library - you can read it for a while, but eventually, you need to return it, and there are rules about what you can do while borrowing it.
In this tutorial, we'll explore:
- What borrowing is and why it matters
- How to create references in Rust
- The rules of borrowing
- Mutable and immutable references
- Common borrowing patterns and real-world examples
What is Borrowing in Rust?
Borrowing in Rust is the act of creating a reference to a value without taking ownership of it. A reference is like a pointer to the data owned by another variable, allowing you to access the data without owning it.
References are created using the &
symbol:
fn main() {
let s1 = String::from("hello");
// Create a reference to s1
let s2 = &s1;
println!("s1: {}", s1);
println!("s2: {}", s2);
}
Output:
s1: hello
s2: hello
In this example, s2
"borrows" s1
's value. The ownership remains with s1
, but s2
can read the data. When s2
goes out of scope, nothing special happens - because s2
doesn't own the data, it doesn't need to free it.
The Rules of Borrowing
Rust enforces two important rules when it comes to borrowing:
-
At any given time, you can have either:
- One mutable reference (
&mut T
) - Any number of immutable references (
&T
)
- One mutable reference (
-
References must always be valid (no dangling references)
Let's look at how these rules work in practice:
Multiple Immutable Borrows
You can have multiple immutable references to a value:
fn main() {
let s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &s;
println!("{}, {}, and {}", r1, r2, r3);
}
Output:
hello, hello, and hello
This works because immutable references don't allow changing the data, so there's no risk of data races or inconsistencies.
Mutable Borrows
To modify a borrowed value, you need a mutable reference:
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
r1.push_str(", world");
println!("{}", r1); // hello, world
}
Output:
hello, world
But you can only have one mutable reference at a time:
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // ERROR: cannot borrow `s` as mutable more than once
println!("{}, {}", r1, r2);
}
This code won't compile because it violates Rust's rule of having only one mutable reference at a time.
Cannot Mix Mutable and Immutable References
You also can't have both mutable and immutable references at the same time:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // immutable borrow
let r2 = &s; // immutable borrow
let r3 = &mut s; // ERROR: cannot borrow `s` as mutable because it is also borrowed as immutable
println!("{}, {}, and {}", r1, r2, r3);
}
This code fails because having both kinds of references could lead to unexpected behavior if the immutable references expect the data to remain unchanged while the mutable reference is modifying it.