Lesson 6: Borrowing and References in Rust
What Are References and Borrowing?
In Rust, ownership ensures only one variable owns any given data at a time, but sometimes, you want to allow functions to read or even modify data without taking ownership. That’s where references and borrowing come into play.
- A reference lets you access data without owning it—essential for sharing or lending values to other parts of your program.
- The act of creating a reference is called borrowing. You “borrow” data for reading (immutable) or modifying (mutable).
Immutable References
You can create any number of immutable references (read-only borrows) at a time.
fn main() {
let str_data = String::from("Hello, world!");
print_length(&str_data);
println!("Still own str_data: {}", str_data);
}
fn print_length(s: &String) {
println!("Length is: {}", s.len());
}
- The
&str_data
creates an immutable reference. - The function receives
&String
and can read but not modify. - Ownership stays with the original variable, so you can use it after the function call.
Mutable References
Mutable references let you modify the borrowed value—but with important restrictions!
- Only one mutable reference to a value can exist at a time—this prevents data races and bugs!
- Pass mutable references with
&mut
.
fn main() {
let mut greeting = String::from("Hello");
change_message(&mut greeting);
println!("Now greeting says: {}", greeting);
}
fn change_message(s: &mut String) {
s.push_str(", world!");
}
Rust’s Reference Rules
Borrowing must follow these rules:
- Any number of immutable references OR a single mutable reference to a value, but never both at the same time.
- References must always point to valid data—Rust ensures safety at compile time.
- References expire at the end of their scope.
Example: Immutable and Mutable References
fn main() {
let mut data = String::from("hello");
let a = &data;
let b = &data;
println!("a: {}, b: {}", a, b); // Both are valid
// Now, after a and b are done, create a mutable reference:
let c = &mut data;
c.push_str(", world");
println!("c: {}", c);
}
Interactive Task 1: Practice Borrowing
Change the following program to use a function that can either read or modify the string. Observe what happens when you try to use both a mutable and an immutable reference at once.
fn main() {
let mut word = String::from("borrow me");
print_str(&word); // pass immutable reference
change_str(&mut word); // pass mutable reference
// Uncomment the next line and see what happens:
// print_str(&word);
}
fn print_str(s: &String) {
println!("{}", s);
}
fn change_str(s: &mut String) {
s.push_str(" please!");
}
Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5ad6af4fa3f67f9a834f8f1796503e93
Mini-Challenge
Write a function make_shouty
that takes a mutable reference to a String
and turns its content uppercase (use .make_ascii_uppercase()
).
- Call your function from
main
and print the result.
fn main() {
let mut cheer = String::from("go rust");
// Your code here!
}
Solution: https://gist.github.com/rust-play/0acc394177bc3012d2e47af4d45d7d58
Recap
- References (&T) let you borrow data for reading or writing.
- Immutable references: Multiple at the same time.
- Mutable references: Only one at a time, enforced by the compiler.
- These rules make Rust programs safe from common concurrency and memory errors.
Questions or code to share? Drop a comment!