Borrowing and References

Welcome back! In Chapter 1: Ownership, we learned about how Rust manages memory using the concept of ownership. We saw that when we pass a variable to a function, ownership is often moved, meaning the original variable can no longer be used. This can be inconvenient! What if we just want to let a function look at our data without taking it away? That's where borrowing and references come in.
Imagine you have a delicious cake (your data). You want to share it with a friend (a function), but you don't want to give them the whole cake! You just want them to have a slice (access to the data). Borrowing and references are like giving your friend a slice without giving up ownership of the entire cake. You still have the cake, and they can enjoy a piece.
What are Borrowing and References?
Borrowing is how Rust lets you access data owned by someone else, like borrowing a book from a friend. References are like bookmarks in the book, pointing to specific pages. There are two kinds of references:
Immutable references: These allow you to read the data but not change it. Think of it as borrowing a library book – you can read it, but you can't write in it.
Mutable references: These allow you to both read and write the data. This is like borrowing a book and being allowed to highlight parts or add notes.
Key Idea: You can have many immutable references, but you can only have one mutable reference at a time. This rule prevents different parts of your code from trying to change the same data at the same time, which could lead to unexpected results. It's like making sure only one person is editing a document at a time to avoid confusion!
Why Use Borrowing and References?
The main reason to use borrowing and references is to avoid moving ownership. This lets you use variables in multiple places without having to constantly transfer ownership back and forth. It's also more efficient, as you're not constantly copying data.
Let's look at a simple example:
fn main() {
let my_string = String::from("hello");
let len = calculate_length(&my_string); // Pass a reference
println!("The length of '{}' is {}.", my_string, len);
}
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
}
Explanation:
We create a
String
calledmy_string
.We call the
calculate_length
function, passing&my_string
. The&
symbol creates a reference tomy_string
.The
calculate_length
function takes a reference to aString
(s: &String
). This means the function can access theString
data without taking ownership.The function returns the length of the string.
Back in
main
, we can still usemy_string
because ownership was never transferred.
Output:
The length of 'hello' is 5.
If we tried to pass my_string
directly to calculate_length
, ownership would be moved, and we couldn't use my_string
afterwards. Borrowing allows us to avoid this.
Immutable References
As we saw in the previous example, immutable references allow you to read data. You create an immutable reference using the &
symbol.
fn main() {
let my_number = 10;
let reference_to_number = &my_number;
println!("The original number is: {}", my_number);
println!("The reference to the number is: {}", reference_to_number);
}
Explanation:
We create a variable
my_number
and assign it the value10
.We create an immutable reference
reference_to_number
that points tomy_number
.We can use both
my_number
andreference_to_number
to access the value10
.
Output:
The original number is: 10
The reference to the number is: 10
You cannot modify the value through an immutable reference. This is important for ensuring data integrity.
Mutable References
Mutable references allow you to modify data. You create a mutable reference using the &mut
symbol.
fn main() {
let mut my_number = 10; // `mut` is important here!
let mutable_reference = &mut my_number;
*mutable_reference += 5; // Dereference and modify
println!("The number is now: {}", my_number);
}
Explanation:
We create a mutable variable
my_number
(notice themut
keyword).We create a mutable reference
mutable_reference
that points tomy_number
.We use the
*
operator to dereference the reference, which allows us to access the actual value that the reference points to. We then add 5 to the value.The original
my_number
is modified!
Output:
The number is now: 15
Important: You can only have one mutable reference to a particular piece of data at a time. Rust enforces this rule to prevent data races and other problems that can occur when multiple parts of your code are trying to modify the same data concurrently. Also, notice we need to declare my_number
as mutable using the mut
keyword. We will discuss Mutability more in the next chapter.
The Borrowing Rules in Action
Let's see the borrowing rules in action:
fn main() {
let mut my_string = String::from("hello");
let ref1 = &my_string; // Immutable borrow
let ref2 = &my_string; // Another immutable borrow
println!("ref1: {}, ref2: {}", ref1, ref2);
let ref3 = &mut my_string; // Mutable borrow - this is OK because ref1 and ref2 are no longer used
ref3.push_str(" world");
println!("ref3: {}", ref3);
}
Explanation:
We can have multiple immutable borrows at the same time.
After the immutable borrows (
ref1
andref2
) are no longer used, we can create a mutable borrow (ref3
).
If we try to use ref1
or ref2
after creating ref3
, the compiler will give us an error. Rust is making sure we don't accidentally read data that's being modified at the same time.
Now, let's see what happens if we try to have both mutable and immutable borrows simultaneously:
fn main() {
let mut my_string = String::from("hello");
let ref1 = &my_string; // Immutable borrow
let ref2 = &mut my_string; // Mutable borrow - ERROR!
println!("ref1: {}", ref1);
println!("ref2: {}", ref2);
}
This code will not compile. The compiler will tell you that you can't have a mutable borrow while you have an immutable borrow. This is a core principle of Rust's borrowing system, preventing data races and ensuring memory safety.
How Borrowing Works Under the Hood
Let's briefly explore what happens behind the scenes (simplified!). Remember how Chapter 1: Ownership mentioned the heap and the stack? When you create a reference, Rust essentially creates a pointer on the stack that points to the data on the heap.
Here's a simplified sequence diagram illustrating borrowing:
sequenceDiagram
participant Main as main()
participant Data as Heap ("hello")
participant R1 as ref1
participant R2 as ref2
Main->>Data: Create String
Data-->>Main: Ownership established
Main->>R1: Create &reference
R1-->>Data: Borrows (read-only)
Main->>R2: Create &reference
R2-->>Data: Borrows (read-only)
Note right of R1: Immutable borrow
Note right of R2: Another immutable borrow
Note right of Main: main() still owns the data
Here's how the struct looks like (covered in Structs):
struct Reference<'a, T: ?Sized> {
data: *const T, // Raw pointer to the data
_marker: PhantomData<&'a T>, // PhantomData to store lifetime information
}
When you create a reference (immutable or mutable), Rust creates a pointer that refers to the data owned by another variable. This pointer stores the address of the data in memory, allowing you to access it without taking ownership. _marker
field is important because it tracks the lifetime of the data.
Conclusion
Borrowing and references are essential tools in Rust for working with data without transferring ownership. By understanding the rules of borrowing (multiple immutable borrows or one mutable borrow at a time), you can write safe and efficient Rust code. We'll be discussing Mutability in the next chapter.
Subscribe to my newsletter
Read articles from Tushar Pamnani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Tushar Pamnani
Tushar Pamnani
I am a MERN stack developer and a blockchain developer from Nagpur. I serve as Management Lead at ML Nagpur Community and The CodeBreakers Club, RCOEM.