Borrowing
Ownership · Lesson 18
A reference &value lets a function look at a value without taking ownership. The caller keeps the value, nothing is copied, and the compiler checks that the reference never outlives what it points at.
Coming from Go&nums is the Rust way to pass a slice: the function sees the same elements and nothing is copied. A Go []int is closest to &mut [i32], since the function can write to the elements. Rust's &[i32] is the read-only form, and the compiler also checks that the reference never outlives nums.
Coming from KotlinA &[i32] parameter is like Kotlin's read-only List<Int>: the function can read but not add or remove. In Kotlin that is only a read-only view, so whoever holds the MutableList can still change it. In Rust no other code can change it while the borrow lasts (for ordinary types).
Coming from PythonPython passes a reference to every argument, so a function can change the caller's list. In Rust you choose: &nums lends the list for reading, &mut nums lends it for changing, and plain nums gives it away for good. A function taking &[i32] can only read it.
Coming from TypeScriptA &[i32] parameter is like readonly number[], but stricter. readonly is only a compile-time check on that one name, and another name for the same array can still change it. In Rust no other code can change it while the borrow lasts (for ordinary types).
Coming from JavaJava hands every object to a method as a reference, so total(nums) looks the same. In Rust you write the & yourself, and a & reference can't change the list at all. Java has no read-only reference type: a final variable can't be reassigned, but the list it holds can still change. The compiler also checks the reference never outlives nums.
Coming from C#A List<int> variable already holds a reference, so the callee sees the same list. The closest typed match for &[i32] is IReadOnlyList<int> or ReadOnlySpan<int>: the function can read but not add. Rust goes further: no other code can change the list while the borrow lasts (for ordinary types). A function that takes Vec<i32> by value takes ownership, and the caller loses it.
Coming from C&[i32] is const int * plus a length, in one value. What C leaves to convention, Rust checks: the function can't write through it, can't free the array, and can't keep it past the array's life. No other code can change or free the array while it's borrowed (in safe Rust, for ordinary types).
Coming from C++&[i32] is std::span<const int>, and &T is const T&. Passing a std::vector by value copies it; a Rust Vec<i32> by value moves, so the caller's nums is gone, and a copy only happens if you write .clone(). Unlike a const T& or a span, a reference in safe Rust can't dangle: the compiler rejects code where it could outlive the value.
The quick comparison
fn total(xs: &[i32]) -> i32 { // & means: borrow, don't take let mut sum = 0; for x in xs { sum += x; } sum}
fn main() { let nums = vec![1, 2, 3]; let t = total(&nums); // lend nums to total println!("{t} {nums:?}"); // nums is still ours}6 [1, 2, 3]
package main
import "fmt"
func total(xs []int) int { sum := 0 for _, x := range xs { sum += x } return sum}
func main() { nums := []int{1, 2, 3} t := total(nums) fmt.Println(t, nums)}6 [1 2 3]
fun total(xs: List<Int>): Int { var sum = 0 for (x in xs) { sum += x } return sum}
fun main() { val nums = listOf(1, 2, 3) val t = total(nums) println("$t $nums")}6 [1, 2, 3]
def total(xs: list[int]) -> int: # gets a reference to the same list return sum(xs)
nums = [1, 2, 3]t = total(nums)print(f"{t} {nums}")6 [1, 2, 3]
// readonly: total promises not to change the arrayfunction total(xs: readonly number[]): number { let sum = 0; for (const x of xs) { sum += x; } return sum;}
const nums = [1, 2, 3];const t = total(nums); // total gets a reference to the same arrayconsole.log(t, nums);6 [ 1, 2, 3 ]
import java.util.List;
public class Main { static int total(List<Integer> xs) { int sum = 0; for (int x : xs) { sum += x; } return sum; }
public static void main(String[] args) { var nums = List.of(1, 2, 3); int t = total(nums); // passes a reference to the same list System.out.println(t + " " + nums); }}6 [1, 2, 3]
List<int> nums = [1, 2, 3];var t = Total(nums); // pass the list inConsole.WriteLine($"{t} [{string.Join(", ", nums)}]"); // nums is still ours
// IReadOnlyList: a promise that Total won't change itstatic int Total(IReadOnlyList<int> xs){ var sum = 0; foreach (var x in xs) { sum += x; } return sum;}6 [1, 2, 3]
#include <stdio.h>
// const int *: read through the pointer, don't change or free itint total(const int *xs, size_t len) { int sum = 0; for (size_t i = 0; i < len; i++) { sum += xs[i]; } return sum;}
int main(void) { int nums[] = {1, 2, 3}; int t = total(nums, 3); // lend nums to total printf("%d [%d, %d, %d]\n", t, nums[0], nums[1], nums[2]); // still ours return 0;}6 [1, 2, 3]
#include <iostream>#include <span>#include <vector>
int total(std::span<const int> xs) { // a read-only view: borrow, don't copy int sum = 0; for (int x : xs) { sum += x; } return sum;}
int main() { std::vector<int> nums{1, 2, 3}; int t = total(nums); // lend nums to total std::cout << t << " ["; // nums is still ours for (std::size_t i = 0; i < nums.size(); ++i) { std::cout << (i ? ", " : "") << nums[i]; } std::cout << "]\n";}6 [1, 2, 3]
What's different
- A reference is an address.
&numsfor aVecis 8 bytes on a 64-bit machine; handed to a function that takes&[i32]it becomes 16: an address and a length. The elements stay where they are on the heap (see the stack and the heap), and the function never gets a copy. - A
&reference lets you read, not change (for ordinary types). Any number of them can exist at once. Changing a value through a reference needs&mut, in the next lesson. - A borrow lasts until its last use, and the value must still exist then. The compiler rejects a reference that could outlive what it points to.
&[i32](a slice: an address and a length) is the usual parameter type for a list. It accepts&Vec<i32>, an array, or part of either. In the same way,&straccepts&String.- The signature tells you what happens to your value:
fn f(x: T)takes it,fn f(x: &T)borrows it to read,fn f(x: &mut T)borrows it to change.
A function that takes the value instead of borrowing it
Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.
Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Works in C. In Rust it's just a rule.Works in C++. In Rust it's just a rule.fn total(xs: Vec<i32>) -> i32 { let mut sum = 0; for x in xs { sum += x; } sum}
fn main() { let nums = vec![1, 2, 3]; let t = total(nums); println!("{t} {nums:?}");}package main
import "fmt"
func total(xs []int) int { sum := 0 for _, x := range xs { sum += x } return sum}
func main() { nums := []int{1, 2, 3} t := total(nums) fmt.Println(t, nums)}6 [1 2 3]
fun total(xs: List<Int>): Int { var sum = 0 for (x in xs) sum += x return sum}
fun main() { val nums = listOf(1, 2, 3) val t = total(nums) println("$t $nums")}6 [1, 2, 3]
def total(xs: list[int]) -> int: result = 0 for x in xs: result += x return result
nums = [1, 2, 3]t = total(nums)print(t, nums)6 [1, 2, 3]
function total(xs: number[]): number { let sum = 0; for (const x of xs) { sum += x; } return sum;}
const nums = [1, 2, 3];const t = total(nums);console.log(t, nums);6 [ 1, 2, 3 ]
import java.util.List;
public class Main { static int total(List<Integer> xs) { int sum = 0; for (int x : xs) { sum += x; } return sum; }
public static void main(String[] args) { var nums = List.of(1, 2, 3); int t = total(nums); System.out.println(t + " " + nums); }}6 [1, 2, 3]
List<int> nums = [1, 2, 3];var t = Total(nums);Console.WriteLine($"{t} [{string.Join(", ", nums)}]");
static int Total(List<int> xs){ var sum = 0; foreach (var x in xs) { sum += x; } return sum;}6 [1, 2, 3]
#include <stdio.h>
int total(int *xs, size_t len) { int sum = 0; for (size_t i = 0; i < len; i++) { sum += xs[i]; } return sum;}
int main(void) { int nums[] = {1, 2, 3}; int t = total(nums, 3); printf("%d [%d, %d, %d]\n", t, nums[0], nums[1], nums[2]); return 0;}6 [1, 2, 3]
#include <iostream>#include <vector>
int total(std::vector<int> xs) { int sum = 0; for (int x : xs) { sum += x; } return sum;}
int main() { std::vector<int> nums{1, 2, 3}; int t = total(nums); std::cout << t << " [" << nums[0] << ", " << nums[1] << ", " << nums[2] << "]\n";}6 [1, 2, 3]
What does the compiler say? Guess first, then open.
error[E0382]: borrow of moved value: `nums` --> src/main.rs:12:20 | 10 | let nums = vec![1, 2, 3]; | ---- move occurs because `nums` has type `Vec<i32>`, which does not implement the `Copy` trait 11 | let t = total(nums); | ---- value moved here 12 | println!("{t} {nums:?}"); | ^^^^ value borrowed here after move | note: consider changing this parameter type in function `total` to borrow instead if owning the value isn't necessary --> src/main.rs:1:14 | 1 | fn total(xs: Vec<i32>) -> i32 { | ----- ^^^^^^^^ this parameter takes ownership of the value | | | in this function help: consider cloning the value if the performance cost is acceptable | 11 | let t = total(nums.clone()); | ++++++++
Go runs this and prints 6 [1 2 3]. Passing a slice shares the same array, so the caller can keep using it. The function could also have written to it, and nothing in the signature says whether it will.
Kotlin runs this and prints 6 [1, 2, 3]. The function gets a reference to the same list, so the caller can keep using it.
Python prints 6 [1, 2, 3]. Passing a list hands the function a reference to the same list, so the caller can keep using it. Nothing in the signature says whether total may change the list.
Prints 6 [ 1, 2, 3 ]. total gets a reference to the same array, and the caller keeps using it. Without readonly in the signature, nothing says whether total may change the array.
Java passes a reference to the same list, and the garbage collector keeps it alive. So nums is still usable after the call.
Passing a List passes a reference, so nums is still usable after the call. The catch is that Total could also change it, and nothing in the signature says it won't.
C passes the array as a pointer, so nums stays usable. Nothing in the type says whether total may keep or free it.
Taking std::vector<int> by value makes a full copy, so nums is still usable and this prints 6 [1, 2, 3]. It works, but you pay for the copy.
xs: Vec<i32> means "give me the Vec". Calling total(nums) moves the list into the function. When total ends, it owns the list, so the list is freed there.
Nothing is wrong with the idea of reading a list twice. Rust just makes the signature say what happens to the value: Vec<i32> means "give me the list", and the other languages hand over a reference or a copy without asking. By the time println! runs, the list was freed when total returned. The fix is not .clone(). It is to say what you meant: the function only needs to look, so it borrows. (The compiler's note: says the same thing. Its help: suggests the simplest fix, not always the best one: .clone() would compile, but it copies the list for no reason.)
The fix
Compiles and runs.
fn total(xs: &[i32]) -> i32 { let mut sum = 0; for x in xs { sum += x; } sum}
fn main() { let nums = vec![1, 2, 3]; let t = total(&nums); println!("{t} {nums:?}");}6 [1, 2, 3]
The toolkit
A reference is a read-only address
A reference is the address of a value that already exists. &x makes one. Through it you can look at x, not change it. It copies nothing and it owns nothing: the value still belongs to its owner, who decides when it is dropped (freed at the end of its scope, as the ownership lesson showed). Making a reference is called borrowing: you look at something that belongs to someone else.
The borrow lasts until the last use of the reference, the last line that mentions it. That is the whole idea. The rest of this lesson is what it buys you, and the errors you get when you break it. The part of the compiler that checks it is the borrow checker.
r and rv are each one address. Nothing was copied: x is still the only 42, and v is still the only owner of the elements. (The arrows to x and v run between stack variables.)fn main() { let x: i32 = 42; let r: &i32 = &x;
println!( "x is {} bytes, r is {} bytes", size_of::<i32>(), size_of::<&i32>() ); println!("*r: {}", *r); // ptr::eq asks whether two references hold the same address. println!("r points at x itself: {}", std::ptr::eq(r, &x));
// A Vec is a 24-byte header plus its elements on the heap. // A reference to the Vec is one address, whatever the Vec holds. let v: Vec<i32> = vec![1, 2, 3]; let rv: &Vec<i32> = &v; println!("v: len {}, capacity {}", v.len(), v.capacity()); println!( "v is {} bytes, rv is {} bytes", size_of::<Vec<i32>>(), size_of::<&Vec<i32>>() ); println!("rv points at v itself: {}", std::ptr::eq(rv, &v)); println!("same elements on the heap: {}", rv.as_ptr() == v.as_ptr());}x is 4 bytes, r is 8 bytes *r: 42 r points at x itself: true v: len 3, capacity 3 v is 24 bytes, rv is 8 bytes rv points at v itself: true same elements on the heap: true
A reference to an i32 is 8 bytes, and so is a reference to a Vec, however much the Vec holds. (Sizes are for the 64-bit Linux ARM machine these outputs come from.) ptr::eq asks whether two references hold the same address: r points at x itself, not at a copy. Values, the stack and the heap has more on sizes.
Reading through a reference
Once you have a &T, you mostly use it as if it were the T:
fn main() { let n = 5; let r: &i32 = &n;
// Arithmetic works through one &. println!("r + 1: {}", r + 1);
// * follows the reference to the value. For a number that is a copy. let copy: i32 = *r; println!("*r: {copy}");
// Comparing needs the same type on both sides. println!("r == &5: {}", r == &5); println!("*r == 5: {}", *r == 5);
// == on two references compares the values they point at, not the addresses. let a = String::from("Ada"); let b = String::from("Ada"); println!("&a == &b: {}", &a == &b); println!("same address: {}", std::ptr::eq(&a, &b));
// Printing shows the value, not the address. println!("r printed: {r}");
// Method calls follow the reference for you. println!("r.pow(2): {}", r.pow(2)); let rn: &String = &a; println!("rn.len(): {}", rn.len());}r + 1: 6 *r: 5 r == &5: true *r == 5: true &a == &b: true same address: false r printed: 5 r.pow(2): 25 rn.len(): 3
- Method calls follow the reference, and
println!shows the value, not the address. r + 1works: Rust defines arithmetic on&i32.*rfollows the reference to the value (for a number, a copy of it). Use it to compare a&i32with a plaini32:*r == 5. Or put&on the other side:r == &5.==on two references compares the values they point at.&a == &bwas true althoughaandbare different Strings at different addresses. To compare addresses, useptr::eq.
Comparing a reference with a plain number does not compile:
fn main() { let n = 5; let r = &n;
if r == 5 { println!("five"); }}error[E0277]: can't compare `&{integer}` with `{integer}` --> src/main.rs:5:10 | 5 | if r == 5 { | ^^ no implementation for `&{integer} == {integer}` | = help: the trait `PartialEq<{integer}>` is not implemented for `&{integer}` help: consider dereferencing here | 5 | if *r == 5 { | +
E0277 means a type can’t do something the code asks of it (a missing trait; the Traits lesson explains). Here the message is “can’t compare &{integer} with {integer}”, where {integer} is the compiler’s name for a number whose exact type isn’t decided yet. The help: shows the fix.
Why borrow: nothing is copied
A function that needs to look at your Vec has three ways to get it:
| You write | What happens | Afterwards |
|---|---|---|
f(v) |
the Vec moves into f (ownership) |
you no longer have it |
f(v.clone()) |
a second buffer is allocated and every element copied | you have the original; the copy is freed when f is done with it |
f(&v) |
f gets an address |
you have it, and nothing was copied |
Here is the difference for 10 MB of data. The function can tell whether it was handed the caller’s buffer or a copy of it:
// `original` is the address of the caller's buffer, so we can tell whether// `xs` is that same buffer or a copy of it.fn report(label: &str, xs: &[u8], original: *const u8) { println!("{label}:"); println!(" len: {}", xs.len()); println!(" same buffer as the caller's: {}", xs.as_ptr() == original);}
fn main() { let data = vec![1u8; 10_000_000]; let original = data.as_ptr();
// Borrow: the function gets an address and a length (16 bytes). report("borrowed", &data, original);
// Clone: allocate 10 MB more and copy every byte into it first. let copy = data.clone(); report("cloned", ©, original); println!("the copy has its own capacity: {}", copy.capacity());
println!("a reference to the data: {} bytes", size_of::<&[u8]>()); println!("data is still ours: {}", data.len());}borrowed: len: 10000000 same buffer as the caller's: true cloned: len: 10000000 same buffer as the caller's: false the copy has its own capacity: 10000000 a reference to the data: 16 bytes data is still ours: 10000000
Cloning is not wrong: for a short string it costs nothing you will notice (when to worry). But borrowing is the default when a function only reads, because it copies nothing (just an address) and it keeps the caller in charge of the value.
Signatures: what a function may do with your value
A function’s parameter types tell the reader, and the compiler, what happens to the value you pass:
fn f(x: T)takes it. You lose it unlessfreturns it. Use this when the function keeps the value.fn f(x: &T)borrows it to read. You keep it. This is the usual choice for a function that only looks.fn f(x: &mut T)borrows it to change. You keep it. That is the next lesson.- A function that makes something new returns it as an owned value, so the caller owns the result.
The ownership lesson has the full table.
Two conventions let a reading function accept the most callers: write &str rather than &String, and &[T] rather than &Vec<T>. A slice, &[i32], is a view of numbers stored in a row: an address and a length. It can cover a whole Vec, an array, or part of one. &str is the same for text.
fn largest(xs: &[i32]) -> i32 { let mut best = xs[0]; for x in xs { if *x > best { best = *x; } } best}
fn is_greeting(text: &str) -> bool { text.starts_with("hello")}
// This function makes something new, so it returns an owned String.fn shout(text: &str) -> String { let mut out = text.to_uppercase(); out.push('!'); out}
fn main() { let list = vec![4, 9, 2]; let array = [7, 1, 8]; println!("largest(&list): {}", largest(&list)); println!("largest(&array): {}", largest(&array)); println!("largest(&list[1..]): {}", largest(&list[1..]));
let owned = String::from("hello world"); println!("is_greeting(&owned): {}", is_greeting(&owned)); println!("is_greeting(\"goodbye\"): {}", is_greeting("goodbye")); println!("is_greeting(&owned[..5]): {}", is_greeting(&owned[..5]));
let loud = shout("hello"); println!("shout(\"hello\"): {loud}"); println!("owned is still ours: {owned}");}largest(&list): 9
largest(&array): 8
largest(&list[1..]): 9
is_greeting(&owned): true
is_greeting("goodbye"): false
is_greeting(&owned[..5]): true
shout("hello"): HELLO!
owned is still ours: hello worldlargest took a Vec, an array and part of a Vec with one signature. is_greeting took a String, a string literal and part of a String. shout builds new text, so it returns a String the caller owns. (Inside largest, for x in xs hands out each item as a reference, so *x is the number itself, as in the last section.) A slice in memory:
fn main() { let v = vec![10, 20, 30, 40]; let all: &[i32] = &v; let tail: &[i32] = &v[1..];
println!("all: len {}", all.len()); println!("tail: len {}", tail.len()); println!( "tail starts {} bytes after all (one i32 is {} bytes)", tail.as_ptr() as usize - all.as_ptr() as usize, size_of::<i32>() ); println!( "each is {} bytes: an address and a length", size_of::<&[i32]>() ); println!( "no copy: all starts where the Vec's elements start: {}", all.as_ptr() == v.as_ptr() );}all: len 4 tail: len 3 tail starts 4 bytes after all (one i32 is 4 bytes) each is 16 bytes: an address and a length no copy: all starts where the Vec's elements start: true
A parameter of type &Vec<i32> or &String is narrower, and the compiler holds you to it:
fn largest(xs: &Vec<i32>) -> i32 { let mut best = xs[0]; for x in xs { if *x > best { best = *x; } } best}
fn main() { let array = [7, 1, 8]; println!("largest(&array): {}", largest(&array));}error[E0308]: mismatched types --> src/main.rs:13:45 | 13 | println!("largest(&array): {}", largest(&array)); | ------- ^^^^^^ expected `&Vec<i32>`, found `&[{integer}; 3]` | | | arguments to this function are incorrect | = note: expected reference `&Vec<i32>` found reference `&[{integer}; 3]` note: function defined here --> src/main.rs:1:4 | 1 | fn largest(xs: &Vec<i32>) -> i32 { | ^^^^^^^ -------------
fn is_greeting(text: &String) -> bool { text.starts_with("hello")}
fn main() { println!( "is_greeting(\"hello there\"): {}", is_greeting("hello there") );}error[E0308]: mismatched types --> src/main.rs:8:21 | 8 | is_greeting("hello there") | ----------- ^^^^^^^^^^^^^ 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 is_greeting(text: &String) -> bool { | ^^^^^^^^^^^ -------------
A &Vec<i32> is a way to look at a Vec, and an array is not one. A &[i32] only needs numbers in a row. The same goes for &String and &str: a string literal is a &str and has no String behind it to point at.
How long a borrow lasts
A borrow lasts from the & to the last use of the reference, not to the end of the block it was written in. Here name is borrowed and then moved. This compiles because r is not used after the move:
fn main() { let name = String::from("Ada");
let r = &name; // the borrow starts println!("r: {r}"); // the last use of r: the borrow ends here
let moved = name; // moving name is fine: nothing borrows it any more println!("moved: {moved}");}r: Ada moved: Ada
Add one more line that uses r after the move, and the borrow is still alive when name moves:
fn main() { let name = String::from("Ada");
let r = &name; // the borrow starts println!("r: {r}");
let moved = name; // name moves while r is still going to be used println!("moved: {moved}");
println!("r again: {r}"); // the borrow is still alive here}error[E0505]: cannot move out of `name` because it is borrowed --> src/main.rs:7:17 | 2 | let name = String::from("Ada"); | ---- binding `name` declared here 3 | 4 | let r = &name; // the borrow starts | ----- borrow of `name` occurs here ... 7 | let moved = name; // name moves while r is still going to be used | ^^^^ move out of `name` occurs here ... 10 | println!("r again: {r}"); // the borrow is still alive here | - borrow later used here | help: consider cloning the value if the performance cost is acceptable | 4 | let r = &name.clone(); // the borrow starts | ++++++++
The error is E0505, “cannot move out of name because it is borrowed”. Read it from the top: where name was borrowed, where it moved, and “borrow later used here”, the line that keeps the borrow alive. The fix: use r before the move, or don’t move name.
let r = &name; ─┐ borrow startsprintln!("r: {r}"); ─┘ last use of r: the borrow ends herelet moved = name; name can move: nothing borrows it
let r = &name; ─┐ borrow startsprintln!("r: {r}"); │let moved = name; │ E0505: name moves while the borrow is aliveprintln!("r again: {r}"); ─┘ last use of rThe other half of the rule: a reference must not outlive the value it points at. In this program let r; declares r with no value yet, and the inner block gives it one. The value x lives only inside that block:
fn main() { let r; { let x = 5; r = &x; } println!("r: {r}");}error[E0597]: `x` does not live long enough --> src/main.rs:5:13 | 4 | let x = 5; | - binding `x` declared here 5 | r = &x; | ^^ borrowed value does not live long enough 6 | } | - `x` dropped here while still borrowed 7 | println!("r: {r}"); | - borrow later used here
E0597, “x does not live long enough”. x is dropped at the closing }, and r is used after it:
x was dropped at }. r still holds its address, which now means nothing. The compiler refuses to compile a use of r here.The compiler knows where each value is dropped (the ownership lesson showed it with Drop). This program compiles, because each reference’s last use comes before its value is dropped:
struct Noisy(&'static str);
impl Drop for Noisy { fn drop(&mut self) { println!(" dropping {}", self.0); }}
fn main() { let outer = Noisy("outer"); let r = &outer; println!("r points at outer: {}", std::ptr::eq(r, &outer)); println!("using r: {}", r.0); println!("the borrow of outer is over; outer lives on");
{ let inner = Noisy("inner"); let r2 = &inner; println!("using r2: {}", r2.0); println!("end of the inner block"); } println!("after the inner block");}r points at outer: true using r: outer the borrow of outer is over; outer lives on using r2: inner end of the inner block dropping inner after the inner block dropping outer
So the errors to recognise are E0382 (a value used after it moved: the gotcha above, fixed by passing &value), E0505 and E0597. Each one shows where the borrow starts and the later line that uses it. A function that returns a reference can also fail with E0106, when the compiler can’t tell which argument the result borrows from; returning references covers it. Stuck on an error you can’t fix? See Working with the Borrow Checker.
Many readers, no writers
Any number of & references to one value can exist at once. For ordinary types none of them can change the value, so they cannot disagree. A function that only reads can be called with each of them:
fn total(xs: &[i32]) -> i32 { let mut sum = 0; for x in xs { sum += x; } sum}
fn largest(xs: &[i32]) -> i32 { let mut best = xs[0]; for x in xs { if *x > best { best = *x; } } best}
fn main() { let scores = vec![30, 95, 60];
// Three borrows, all alive at once. let a = &scores; let b = &scores; let c = &scores[1..]; println!("a and b are the same Vec: {}", std::ptr::eq(a, b));
println!("total(a): {}", total(a)); println!("largest(b): {}", largest(b)); println!("total(c): {}", total(c)); println!("scores: {scores:?}");}a and b are the same Vec: true total(a): 185 largest(b): 95 total(c): 155 scores: [30, 95, 60]
a, b and c were all alive at the same time, and each function read through its own: many readers. (largest needs *x to compare numbers. sum += x in total compiles without a star, because Rust can add a &i32 to an i32, as the program shows.)
Through a & you can’t change an ordinary value:
fn add_bonus(scores: &Vec<i32>) { scores.push(100);}
fn main() { let scores = vec![30, 95, 60]; add_bonus(&scores); println!("{scores:?}");}error[E0596]: cannot borrow `*scores` as mutable, as it is behind a `&` reference --> src/main.rs:2:5 | 2 | scores.push(100); | ^^^^^^ `scores` is a `&` reference, so it cannot be borrowed as mutable | help: consider changing this to be a mutable reference | 1 | fn add_bonus(scores: &mut Vec<i32>) { | +++
E0596 says scores is a & reference, and the help: is the answer: a &mut. The rules for &mut are the subject of the next lesson. In short, while a &mut exists, nothing else may use the value at all: many readers, or one writer, never both.
Practice
Try it
Each exercise is a small program with checks in main. Open it in the Playground, replace the todo!(), and run it until it prints all checks passed. In all three the signatures are already right; the point is to write the bodies that read through the references and leave the caller’s values alone.
Count the words
Count the words in a &str with a loop. Words are separated by any whitespace. The checks pass a literal, a borrowed String that must still be usable afterwards, part of a String, empty and blank text, and text that is not English.
/// Count the words in `text`. Words are separated by any whitespace: spaces, tabs,/// newlines, or several in a row. The function only reads the text, so it must borrow it.fn count_words(text: &str) -> usize { todo!("loop over text.split_whitespace() and count")}
fn main() { // A &str literal works. assert_eq!(count_words("the quick brown fox"), 4);
// So does a borrowed String, and the String is still ours afterwards. let line = String::from(" one \t two\nthree "); assert_eq!(count_words(&line), 3); assert_eq!(line.len(), 19);
// So does part of a String. assert_eq!(count_words(&line[6..]), 2);
// Nothing to count. assert_eq!(count_words(""), 0); assert_eq!(count_words(" \n\t "), 0);
// Not English: split_whitespace works on characters, not bytes. assert_eq!(count_words("café crème 東京"), 3);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// Count the words in `text`. Words are separated by any whitespace: spaces, tabs,/// newlines, or several in a row. The function only reads the text, so it must borrow it.fn count_words(text: &str) -> usize { let mut count = 0; for _word in text.split_whitespace() { count += 1; } count}
fn main() { // A &str literal works. assert_eq!(count_words("the quick brown fox"), 4);
// So does a borrowed String, and the String is still ours afterwards. let line = String::from(" one \t two\nthree "); assert_eq!(count_words(&line), 3); assert_eq!(line.len(), 19);
// So does part of a String. assert_eq!(count_words(&line[6..]), 2);
// Nothing to count. assert_eq!(count_words(""), 0); assert_eq!(count_words(" \n\t "), 0);
// Not English: split_whitespace works on characters, not bytes. assert_eq!(count_words("café crème 東京"), 3);
println!("all checks passed");}all checks passed
Count the matches
Count how many items of a &[i32] equal a &i32. Both are references, so x in your loop is a &i32 too. Pay attention to the rules in reading through a reference. One of the checks passes a reference to an item of the same Vec it is searching.
/// How many items in `xs` equal `target`? Both are borrowed: the numbers are not copied/// into the function and nothing is used up.fn count_matches(xs: &[i32], target: &i32) -> usize { todo!("for x in xs gives &i32 items; compare them with target, or compare *x with *target")}
fn main() { let list = vec![4, 7, 4, 9, 4];
// A reference to a literal. assert_eq!(count_matches(&list, &4), 3); assert_eq!(count_matches(&list, &5), 0);
// A reference into the same Vec we are searching: two borrows alive at once. assert_eq!(count_matches(&list, &list[1]), 1); assert_eq!(count_matches(&list, &list[0]), 3);
// Part of the Vec, and an array. assert_eq!(count_matches(&list[1..], &4), 2); assert_eq!(count_matches(&[1, 1, 2], &1), 2);
// Nothing to search. assert_eq!(count_matches(&[], &1), 0);
// The Vec was only borrowed. assert_eq!(list.len(), 5);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
/// How many items in `xs` equal `target`? Both are borrowed: the numbers are not copied/// into the function and nothing is used up.fn count_matches(xs: &[i32], target: &i32) -> usize { let mut count = 0; for x in xs { if x == target { count += 1; } } count}
fn main() { let list = vec![4, 7, 4, 9, 4];
// A reference to a literal. assert_eq!(count_matches(&list, &4), 3); assert_eq!(count_matches(&list, &5), 0);
// A reference into the same Vec we are searching: two borrows alive at once. assert_eq!(count_matches(&list, &list[1]), 1); assert_eq!(count_matches(&list, &list[0]), 3);
// Part of the Vec, and an array. assert_eq!(count_matches(&list[1..], &4), 2); assert_eq!(count_matches(&[1, 1, 2], &1), 2);
// Nothing to search. assert_eq!(count_matches(&[], &1), 0);
// The Vec was only borrowed. assert_eq!(list.len(), 5);
println!("all checks passed");}all checks passed
Report on a player
Two functions that read a Player through &Player: best returns the highest score (0 when there are none), and report builds a String like ana: best 95 in 3 games, calling best along the way. Calling best(player) from inside report does not use player up. Why not? player is a &Player, and any number of readers are allowed.
struct Player { name: String, scores: Vec<u32>,}
/// The highest score, or 0 when there are none. Only reads the player.fn best(player: &Player) -> u32 { todo!("loop over &player.scores and keep the largest")}
/// A line like "ana: best 95 in 3 games". Builds a new String and only reads the player.fn report(player: &Player) -> String { todo!("call best(player), then format! the name, the best score and scores.len()")}
fn main() { let ana = Player { name: String::from("ana"), scores: vec![30, 95, 60], };
// Reading the same player twice only works if report borrows it. let first = report(&ana); let second = report(&ana); assert_eq!(first, "ana: best 95 in 3 games"); assert_eq!(first, second);
// And we can still use the player. assert_eq!(ana.name, "ana"); assert_eq!(best(&ana), 95);
// A player with no scores. let new = Player { name: String::from("zoë"), scores: Vec::new(), }; assert_eq!(best(&new), 0); assert_eq!(report(&new), "zoë: best 0 in 0 games");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
struct Player { name: String, scores: Vec<u32>,}
/// The highest score, or 0 when there are none. Only reads the player.fn best(player: &Player) -> u32 { let mut top = 0; for score in &player.scores { if *score > top { top = *score; } } top}
/// A line like "ana: best 95 in 3 games". Builds a new String and only reads the player.fn report(player: &Player) -> String { let top = best(player); format!( "{}: best {} in {} games", player.name, top, player.scores.len() )}
fn main() { let ana = Player { name: String::from("ana"), scores: vec![30, 95, 60], };
// Reading the same player twice only works if report borrows it. let first = report(&ana); let second = report(&ana); assert_eq!(first, "ana: best 95 in 3 games"); assert_eq!(first, second);
// And we can still use the player. assert_eq!(ana.name, "ana"); assert_eq!(best(&ana), 95);
// A player with no scores. let new = Player { name: String::from("zoë"), scores: Vec::new(), }; assert_eq!(best(&new), 0); assert_eq!(report(&new), "zoë: best 0 in 0 games");
println!("all checks passed");}all checks passed
Going deeper (optional)
Nothing here is needed for the exercises.
What the compiler is checking
Every error above comes from two rules:
- A reference must never outlive the value it points to. (E0597, and E0515 when a function returns a reference to its own local.)
- While a value is borrowed, nothing may change it, move it or drop it. (E0505 for a move, E0502 and E0499 for a change; the next lesson.)
Together they mean that in safe Rust, a reference you hold points at a value that still exists and that no other code changes, apart from the interior-mutability types. In safe Rust no reference can read freed memory. Those types change through a shared reference on purpose, and stay safe in other ways: Cell only copies values in and out, RefCell checks at run time, and Mutex and atomics coordinate threads (Shared state).
The checks happen when the program is compiled and add no instructions to the running program: a reference is just an address. The checking is also local: the compiler looks at one function at a time, and a function’s signature is all it knows about the others. That is why a function that returns a reference has to say which argument it borrows from, when more than one could be the source.
One detail about “last use”: if the reference sits inside a value that has a Drop, running the drop counts as a use. Guard<'a> holds a borrowed name ('a names how long the borrow lasts; Lifetimes comes later). Moving name fails, because guard’s drop runs at the end of main and reads name:
struct Guard<'a>(&'a String);
impl Drop for Guard<'_> { fn drop(&mut self) { println!("dropping a guard for {}", self.0); }}
fn main() { let name = String::from("Ada"); let guard = Guard(&name);
let moved = name; // Guard::drop will still read the borrowed name println!("moved: {moved}");}error[E0505]: cannot move out of `name` because it is borrowed --> src/main.rs:13:17 | 10 | let name = String::from("Ada"); | ---- binding `name` declared here 11 | let guard = Guard(&name); | ----- borrow of `name` occurs here 12 | 13 | let moved = name; // Guard::drop will still read the borrowed name | ^^^^ move out of `name` occurs here 14 | println!("moved: {moved}"); 15 | } | - borrow might be used here, when `guard` is dropped and runs the `Drop` code for type `Guard` | help: consider cloning the value if the performance cost is acceptable | 11 | let guard = Guard(&name.clone()); | ++++++++
&String becomes &str by itself
Why did is_greeting(&owned) work when owned is a String and the parameter is a &str? Where the compiler knows which type a reference should have, such as a function argument or a let with a written type, it converts a &String to a &str for you. This is deref coercion. It works because String implements Deref<Target = str> (Traits explains). &Vec<T> to &[T] works the same way. The conversion goes from the type that implements Deref to its target, not back.
use std::any::type_name_of_val as type_of;
fn show(text: &str) { println!("show got {}: {text}", type_of(&text));}
fn main() { let name = String::from("Ada"); let r = &name; println!("r is {}", type_of(&r));
// The function wants &str and r is &String. The compiler converts for us. show(r);
// Same for a let with a written type. let text: &str = r; println!("text is {}", type_of(&text)); println!("same text, no copy: {}", text.as_ptr() == name.as_ptr());
// A &Vec<i32> becomes a &[i32] the same way. let v = vec![1, 2, 3]; let slice: &[i32] = &v; println!("slice is {} with len {}", type_of(&slice), slice.len());}r is &alloc::string::String show got &str: Ada text is &str same text, no copy: true slice is &[i32] with len 3
r really is a &String (alloc::string::String is the full name of String). Passing it to show, or storing it in a &str, gave a &str that points at the same bytes. Nothing was copied.
Under the hood this is a reborrow: *r is the value r points at, and &*r borrows it again. You will see &*name in real code, to pick a type.
fn main() { let name = String::from("Ada"); let a: &String = &name;
// *a is the String itself. &*a borrows it again: a new reference, same String. let b: &String = &*a; println!("b points at the same String as a: {}", std::ptr::eq(a, b));
// *name is the str inside the String, so &*name is a &str. This is the // conversion the compiler does for you when it turns a &String into a &str. let text: &str = &*name; println!("text is the same bytes: {}", text.as_ptr() == name.as_ptr()); println!("text: {text}, len {}", text.len());}b points at the same String as a: true text is the same bytes: true text: Ada, len 3
References are Copy
A &T is Copy, like a number: passing it on, or assigning it to another variable, makes a second reference and leaves the first usable. A String owns its buffer, so copying it would mean two owners. A &String only points, so duplicating the pointer is harmless. &T is Copy so that many readers can share one value. (&mut T follows different rules: next lesson.)
fn show(text: &String) { println!("show: {text}");}
fn main() { let name = String::from("Ada"); let r = &name;
// Passing r does not use it up: &String is Copy. show(r); show(r);
let r2 = r; // copies the reference; r is still usable println!("r and r2 point at the same String: {}", std::ptr::eq(r, r2));
// A String is not Copy: this moves it. let moved = name; println!("moved: {moved}");}show: Ada show: Ada r and r2 point at the same String: true moved: Ada
Borrowing in other languages
Read more
- Reading compiler errors: how an error is laid out, and every error code on this site with what it means and where to see it.
- The Rust Book, chapter 4.2: References and Borrowing, which also introduces
&mut. - Rust by Example: Borrowing, and its pages on the
refpattern and on lifetimes. - The Rust Reference: Reference types, and Type coercions for the exact deref coercion rules.
- The
Dereftrait, including its section on coercion. std::ptr::eq, for comparing addresses.- Error index: E0505 and E0597.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.