Lesson 14 of 94
References & Borrowing
The previous lesson ended with a problem: a function that only reads a value should not have to take ownership of it and give it back. The solution is a reference, written &value. Creating a reference is called borrowing: you get to use the value, but the owner keeps it.
- References are explicit:
&T. &Tis read-only. Modifying needs&mut T(next lesson)- The compiler proves every reference ends before its owner drops the value.
- A reference is never null.
==on references compares the values.
1. Borrowing a value with &
&text creates a reference to text. The function's parameter type &String means "a reference to a String".
fn print_length(s: &String) {
println!("'{s}' is {} bytes long", s.len());
}
fn main() {
let text = String::from("borrowed, not moved");
print_length(&text);
print_length(&text);
println!("I still own: {text}");
}'borrowed, not moved' is 19 bytes long
'borrowed, not moved' is 19 bytes long
I still own: borrowed, not movedCompare with Ownership & Functions example 1: the only changes are &String in the parameter and &text at the call, and now text is still usable afterwards.
2. What a reference is in memory
A reference is a pointer to the value. It does not own the value, so when the reference goes away, nothing is freed.
s (&String) text (String) heap
┌─────┐ ┌──────────┬────┐ ┌─────────────────────┐
│ ptr ├─────────► │ ptr │ ───┼──────► │ borrowed, not moved │
└─────┘ │ len │ 19 │ └─────────────────────┘
│ capacity │ 19 │
└──────────┴────┘The rules the compiler enforces for references are explained in the rest of this lesson and the next one.
3. The owner is not affected when a borrow ends
fn shout(s: &String) -> String {
s.to_uppercase()
} // `s` goes out of scope, but it owns nothing, so nothing is dropped
fn main() {
let word = String::from("hello");
let loud = shout(&word);
println!("{word} -> {loud}");
}hello -> HELLO4. Many shared references at the same time
You can have any number of & references to a value at once. Reading from several places at the same time is always safe.
fn main() {
let config = String::from("timeout=30");
let r1 = &config;
let r2 = &config;
let r3 = &config;
println!("{r1} | {r2} | {r3}");
}timeout=30 | timeout=30 | timeout=30This is why &T is called a shared reference.
5. References are Copy
Copying a shared reference just makes another pointer to the same value, so references themselves are Copy.
fn main() {
let owner = String::from("data");
let a = &owner;
let b = a; // copies the reference; `a` is still valid
println!("{a} {b} {owner}");
}data data data6. Shared references are read-only
You cannot change a value through a & reference.
fn add_suffix(s: &String) {
s.push_str("!");
}
fn main() {
let text = String::from("hi");
add_suffix(&text);
println!("{text}");
}error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | s.push_str("!");
| ^ `s` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_suffix(s: &mut String) {
| +++Changing a borrowed value needs a mutable reference, &mut, which comes with extra rules. That is the next lesson.
7. Dereferencing with *
*r means "the value that r points to". You need it when working with the value itself, for example in arithmetic on a reference to a number.
fn main() {
let price = 250;
let r = &price;
let with_tax = *r + 50;
println!("r points to {}, with tax {with_tax}", *r);
println!("same comparison: {}", *r == 250);
}r points to 250, with tax 300
same comparison: true8. Automatic dereferencing
In most places, Rust dereferences for you, so you rarely write *:
- method calls:
r.len()works on a&String(and even on a&&String), println!prints the value a reference points to,- comparing two references with
==compares the values.
fn main() {
let name = String::from("Ferris");
let r = &name;
let rr = &r; // a reference to a reference
println!("{} {} {}", name.len(), r.len(), rr.len());
println!("{name} {r} {rr}");
}6 6 6
Ferris Ferris Ferris9. == on references compares values
== on two references asks "equal values?". To check whether two references point to the same place, use std::ptr::eq.
fn main() {
let a = String::from("rust");
let b = String::from("rust");
let ra = &a;
let rb = &b;
println!("values equal: {}", ra == rb);
println!("same object: {}", std::ptr::eq(ra, rb));
println!("same object: {}", std::ptr::eq(ra, &a));
}values equal: true
same object: false
same object: true10. Comparing a reference with a plain value
Comparing &i32 with i32 directly is an error, because they are different types. Dereference one side, or borrow the other.
fn main() {
let limit = 10;
let r = &limit;
println!("{}", r == 10);
}error[E0277]: can't compare `&{integer}` with `{integer}`
--> src/main.rs:4:22
|
4 | println!("{}", r == 10);
| ^^ no implementation for `&{integer} == {integer}`
|
= help: the trait `PartialEq<{integer}>` is not implemented for `&{integer}`
help: consider dereferencing here
|
4 | println!("{}", *r == 10);
| +fn main() {
let limit = 10;
let r = &limit;
println!("{}", *r == 10);
println!("{}", r == &10);
}true
true11. Borrowing an element of a Vec
&v[i] borrows one element. This is the usual fix for "cannot move out of index" from Ownership.
fn main() {
let names = vec![String::from("alpha"), String::from("beta")];
let first = &names[0];
println!("first: {first}, length {}", first.len());
println!("all: {:?}", names);
}first: alpha, length 5
all: ["alpha", "beta"]12. for x in &v gives you references
Looping over &v borrows the vector, and each x is a reference to one element, &T.
fn main() {
let stock = vec![3, 0, 12, 0, 7];
let mut out_of_stock = 0;
for count in &stock {
if *count == 0 {
out_of_stock += 1;
}
}
println!("{out_of_stock} products out of stock: {:?}", stock);
}2 products out of stock: [3, 0, 12, 0, 7]count has type &i32, so *count gets the number. Writing count == &0 would also work.
13. Destructuring a reference in the loop pattern
For Copy elements, write for &x in &v. The pattern &x takes the reference apart and copies out the value, so x is a plain i32.
fn main() {
let readings = vec![12, 45, 7, 30];
let mut max = 0;
for &r in &readings {
if r > max {
max = r;
}
}
println!("max reading: {max}");
}max reading: 4514. Borrowing a whole Vec in a function
fn average(values: &Vec<f64>) -> f64 {
let mut sum = 0.0;
for v in values {
sum += v;
}
sum / values.len() as f64
}
fn main() {
let response_times = vec![120.0, 95.5, 210.25, 88.0];
println!("average: {:.2} ms", average(&response_times));
println!("based on {} samples", response_times.len());
}average: 128.44 ms
based on 4 samplesInside the function, for v in values loops over a &Vec<f64>, which already gives references, and sum += v works directly on the reference because numeric operators accept references too.
15. Several functions reading the same data
Because each function only borrows, the caller can hand the same data to as many functions as it likes.
fn min(v: &Vec<i32>) -> i32 {
let mut m = v[0];
for &x in v {
if x < m { m = x; }
}
m
}
fn max(v: &Vec<i32>) -> i32 {
let mut m = v[0];
for &x in v {
if x > m { m = x; }
}
m
}
fn sum(v: &Vec<i32>) -> i32 {
let mut s = 0;
for &x in v {
s += x;
}
s
}
fn main() {
let daily_sales = vec![340, 120, 560, 410, 290];
println!("min={} max={} total={}", min(&daily_sales), max(&daily_sales), sum(&daily_sales));
}min=120 max=560 total=172016. Borrowing parts of a tuple
fn main() {
let employee = (String::from("Grace"), String::from("Engineering"), 8_250);
let name = &employee.0;
let dept = &employee.1;
println!("{name} works in {dept}");
println!("whole record still here: {:?}", employee);
}Grace works in Engineering
whole record still here: ("Grace", "Engineering", 8250)17. A function can return a reference into its argument
When a function receives a reference, it can return a reference to part of that data. The result borrows from the same owner as the argument.
fn longest(names: &Vec<String>) -> &String {
let mut best = &names[0];
for name in names {
if name.len() > best.len() {
best = name;
}
}
best
}
fn main() {
let names = vec![
String::from("Ana"),
String::from("Bartholomew"),
String::from("Chen"),
];
let winner = longest(&names);
println!("longest name: {winner}");
}longest name: BartholomewThe compiler knows the returned reference comes from names, because it is the only reference parameter. Rules like this are called lifetime elision. When a function takes two references, you sometimes have to tell the compiler which one the result borrows from; that is the Lifetimes section.
18. A reference cannot outlive the value it points to
The compiler checks that every reference is used only while the value it points to still exists. Here r would point to temp after temp has been dropped.
fn main() {
let r;
{
let temp = String::from("short-lived");
r = &temp;
}
println!("{r}");
}error[E0597]: `temp` does not live long enough
--> src/main.rs:5:13
|
4 | let temp = String::from("short-lived");
| ---- binding `temp` declared here
5 | r = &temp;
| ^^^^^ borrowed value does not live long enough
6 | }
| - `temp` dropped here while still borrowed
7 | println!("{r}");
| - borrow later used hereRust has no garbage collector, so the compiler proves at compile time that this never happens. The borrowed value does not live long enough error is the heart of Rust's memory safety.
19. The fix: make the owner live long enough
fn main() {
let temp = String::from("long enough");
let r;
{
r = &temp;
}
println!("{r}");
}long enough20. You cannot move a value while it is borrowed
Moving data while view still points to it would leave view dangling, so the compiler rejects the move.
fn main() {
let data = vec![1, 2, 3];
let view = &data;
let new_owner = data;
println!("{:?}", view);
}error[E0505]: cannot move out of `data` because it is borrowed
--> src/main.rs:4:21
|
2 | let data = vec![1, 2, 3];
| ---- binding `data` declared here
3 | let view = &data;
| ----- borrow of `data` occurs here
4 | let new_owner = data;
| ^^^^ move out of `data` occurs here
5 | println!("{:?}", view);
| ---- borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let view = &data.clone();
| ++++++++21. A borrow ends at its last use
A borrow lasts from where the reference is created until the last place it is used, not until the end of the scope. After the last use, the owner is free again. Here the move is fine, because view is not used after it.
fn main() {
let data = vec![1, 2, 3];
let view = &data;
println!("view: {:?}", view); // last use of `view`
let new_owner = data; // fine: no borrow is active any more
println!("moved: {:?}", new_owner);
}view: [1, 2, 3]
moved: [1, 2, 3]This behavior is called non-lexical lifetimes (NLL). It makes the borrow checker much less strict than it looks at first.
22. &str accepts both literals and Strings
A parameter of type &String only accepts references to a String. A string literal is not a String, so it does not fit:
fn greet(name: &String) {
println!("Hello, {name}!");
}
fn main() {
greet("literal");
}error[E0308]: mismatched types
--> src/main.rs:6:11
|
6 | greet("literal");
| ----- ^^^^^^^^^ expected `&String`, found `&str`
| |
| arguments to this function are incorrect
|
= note: expected reference `&String`
found reference `&'static str`
note: function defined here
--> src/main.rs:1:4
|
1 | fn greet(name: &String) {
| ^^^^^ -------------Use &str instead. It accepts string literals and &String (Rust converts &String to &str automatically). This is why almost every Rust function that reads text takes &str. Slices explains what &str is.
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn main() {
let owned = String::from("Ferris");
greet("literal");
greet(&owned);
}Hello, literal!
Hello, Ferris!23. Realistic example: validating a sign-up form
Each check borrows the input. The function collects error messages into a new Vec<String> that it owns and returns.
fn validate_signup(username: &str, email: &str, password: &str) -> Vec<String> {
let mut errors = Vec::new();
if username.len() < 3 {
errors.push(format!("username '{username}' is too short"));
}
if !email.contains('@') {
errors.push(format!("email '{email}' is not valid"));
}
if password.len() < 12 {
errors.push(String::from("password must be at least 12 characters"));
}
if password.contains(username) {
errors.push(String::from("password must not contain the username"));
}
errors
}
fn main() {
let username = String::from("al");
let email = String::from("al.example.com");
let password = String::from("al-password");
let errors = validate_signup(&username, &email, &password);
println!("{} problem(s):", errors.len());
for e in &errors {
println!(" - {e}");
}
println!("the form data is still ours: {username}, {email}");
}4 problem(s):
- username 'al' is too short
- email 'al.example.com' is not valid
- password must be at least 12 characters
- password must not contain the username
the form data is still ours: al, al.example.com24. Realistic example: finding the cheapest product
The function returns a reference to an element inside the catalog, so nothing is copied.
fn cheapest(catalog: &Vec<(String, f64)>) -> &(String, f64) {
let mut best = &catalog[0];
for item in catalog {
if item.1 < best.1 {
best = item;
}
}
best
}
fn main() {
let catalog = vec![
(String::from("mechanical keyboard"), 129.0),
(String::from("mouse"), 39.5),
(String::from("monitor stand"), 45.0),
];
let (name, price) = cheapest(&catalog);
println!("cheapest: {name} at {price:.2}");
println!("catalog still has {} items", catalog.len());
}cheapest: mouse at 39.50
catalog still has 3 itemslet (name, price) = cheapest(&catalog); destructures a reference to a tuple, so name is a &String and price is a &f64. Rust applies the pattern through the reference automatically.
25. Realistic example: reading the ledger without taking it
This is the ledger from the previous lesson. balance_of now borrows the ledger, so it returns just the number, and the caller keeps ownership.
fn balance_of(ledger: &Vec<(String, i64)>, account: &str) -> i64 {
for (name, amount) in ledger {
if name == account {
return *amount;
}
}
0
}
fn total(ledger: &Vec<(String, i64)>) -> i64 {
let mut sum = 0;
for (_, amount) in ledger {
sum += amount;
}
sum
}
fn main() {
let ledger = vec![(String::from("alice"), 125), (String::from("bob"), 60)];
println!("alice={}", balance_of(&ledger, "alice"));
println!("bob={}", balance_of(&ledger, "bob"));
println!("carol={}", balance_of(&ledger, "carol"));
println!("total={}", total(&ledger));
}alice=125
bob=60
carol=0
total=18526. Realistic example: a report from shared data
Several report sections read the same order list. None of them needs ownership, so they all borrow.
fn section_count(orders: &Vec<(u32, String, f64)>) -> String {
format!("Orders: {}", orders.len())
}
fn section_revenue(orders: &Vec<(u32, String, f64)>) -> String {
let mut revenue = 0.0;
for (_, _, amount) in orders {
revenue += amount;
}
format!("Revenue: {revenue:.2}")
}
fn section_top_customer(orders: &Vec<(u32, String, f64)>) -> String {
let mut top = &orders[0];
for order in orders {
if order.2 > top.2 {
top = order;
}
}
format!("Largest order: #{} by {} ({:.2})", top.0, top.1, top.2)
}
fn main() {
let orders = vec![
(1001, String::from("acme"), 250.0),
(1002, String::from("globex"), 1_200.5),
(1003, String::from("initech"), 89.99),
];
let report = vec![
section_count(&orders),
section_revenue(&orders),
section_top_customer(&orders),
];
println!("{}", report.join("\n"));
}Orders: 3
Revenue: 1540.49
Largest order: #1002 by globex (1200.50)27. Realistic example: counting matches across two lists
Both lists are borrowed at the same time, which is always allowed for shared references.
fn count_common(a: &Vec<String>, b: &Vec<String>) -> usize {
let mut count = 0;
for x in a {
for y in b {
if x == y {
count += 1;
}
}
}
count
}
fn main() {
let followers = vec![String::from("ana"), String::from("ben"), String::from("cy")];
let following = vec![String::from("cy"), String::from("dee"), String::from("ana")];
println!("mutual connections: {}", count_common(&followers, &following));
println!("self-overlap: {}", count_common(&followers, &followers));
}mutual connections: 2
self-overlap: 3The last line borrows followers twice at the same time, which is fine: both borrows only read.
Summary
&valueborrows a value without taking ownership. The type of a reference toTis&T.- You can have any number of shared references at once. They are read-only and
Copy. *rdereferences. Method calls,println!, and==dereference automatically.==on references compares values.std::ptr::eqcompares addresses.- A reference can never outlive the value it points to (
E0597), and a borrowed value cannot be moved (E0505). - A borrow lasts until its last use, not until the end of the scope.
- Prefer
&strover&Stringfor text parameters.
Next: Mutable References, where you learn to change borrowed values and meet the rule that makes Rust free of data races.
