Skip to content
Learn Rust
Theme

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

Rustsrc/main.rsRun ↗
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
}
$ cargo run
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)
}
$ go run main.go
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")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
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}")
$ python3 main.py
6 [1, 2, 3]
// readonly: total promises not to change the array
function 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 array
console.log(t, nums);
$ tsc --strict --module nodenext --types node main.ts && node main.js
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);
}
}
$ javac Main.java && java Main
6 [1, 2, 3]
List<int> nums = [1, 2, 3];
var t = Total(nums); // pass the list in
Console.WriteLine($"{t} [{string.Join(", ", nums)}]"); // nums is still ours
// IReadOnlyList: a promise that Total won't change it
static int Total(IReadOnlyList<int> xs)
{
var sum = 0;
foreach (var x in xs)
{
sum += x;
}
return sum;
}
$ dotnet run
6 [1, 2, 3]
#include <stdio.h>
// const int *: read through the pointer, don't change or free it
int 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;
}
$ gcc -Wall -Wextra main.c -o main && ./main
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
6 [1, 2, 3]

What's different

  • A reference is an address. &nums for a Vec is 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, &str accepts &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.
Gotcha

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.
Rustsrc/main.rs✗ won't compile
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)
}
$ go run main.go✓ runs
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")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
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)
$ python3 main.py✓ runs
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);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
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);
}
}
$ javac Main.java && java Main✓ runs
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;
}
$ dotnet run✓ runs
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;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
6 [1, 2, 3]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0382?
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());
   |                       ++++++++
What happens in Go

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.

What happens in Kotlin

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.

What happens in Python

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.

What happens in TypeScript

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.

What happens in Java

Java passes a reference to the same list, and the garbage collector keeps it alive. So nums is still usable after the call.

What happens in C#

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.

What happens in C

C passes the array as a pointer, so nums stays usable. Nothing in the type says whether total may keep or free it.

What happens in C++

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.

What Rust sees

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.

Why Rust says no

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.

Rust, fixedsrc/main.rsRun ↗
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:?}");
}
$ cargo run
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.

let x = 42; let r = &x; let v = vec![1, 2, 3]; let rv = &v;
STACKHEAPx: i3242r: &i32address of xv: Vec<i32>ptrlen 3cap 3rv: &Vec<i32>address of vpoints at v, not at the elements123
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.)
Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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 + 1 works: Rust defines arithmetic on &i32.
  • *r follows the reference to the value (for a number, a copy of it). Use it to compare a &i32 with a plain i32: *r == 5. Or put & on the other side: r == &5.
  • == on two references compares the values they point at. &a == &b was true although a and b are different Strings at different addresses. To compare addresses, use ptr::eq.

Comparing a reference with a plain number does not compile:

Rustsrc/main.rs✗ won't compile
fn main() {
let n = 5;
let r = &n;
if r == 5 {
println!("five");
}
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
// `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", &copy, 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());
}
$ cargo run
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
let data = vec![1u8; 10_000_000]; let copy = data.clone(); report(…, &data, …)
STACKHEAPdata: Vec<u8>ptrlen 10000000cap 10000000copy: Vec<u8>ptrlen 10000000cap 10000000clone(): a second 10 MB bufferxs: &[u8]addresslen 10000000borrowed: 16 bytes, the same buffer as datadata's buffer: 10,000,000 bytes111…copy's buffer: 10,000,000 bytes111…
The borrow is a 16-byte address and length pointing into the buffer that is already there. The clone had to allocate and fill a whole new one.

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 unless f returns 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.

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
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 world

largest 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:

let v = vec![10, 20, 30, 40]; let all: &[i32] = &v; let tail: &[i32] = &v[1..];
STACKHEAPv: Vec<i32>ptrlen 4cap 4all: &[i32]addresslen 4starts at the first elementtail: &[i32]addresslen 3starts at the second element, 4 bytes further on10203040
Two views of the same four numbers. Neither copied anything.
Rustsrc/main.rsRun ↗
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()
);
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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));
}
$ cargo buildWhat is E0308?
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 {
   |    ^^^^^^^ -------------
Rustsrc/main.rs✗ won't compile
fn is_greeting(text: &String) -> bool {
text.starts_with("hello")
}
fn main() {
println!(
"is_greeting(\"hello there\"): {}",
is_greeting("hello there")
);
}
$ cargo buildWhat is E0308?
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:

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
r: Ada
moved: Ada

Add one more line that uses r after the move, and the borrow is still alive when name moves:

Rustsrc/main.rs✗ won't compile
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
}
$ cargo buildWhat is E0505?
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 starts
println!("r: {r}"); ─┘ last use of r: the borrow ends here
let moved = name; name can move: nothing borrows it
let r = &name; ─┐ borrow starts
println!("r: {r}"); │
let moved = name; │ E0505: name moves while the borrow is alive
println!("r again: {r}"); ─┘ last use of r

The 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:

Rustsrc/main.rs✗ won't compile
fn main() {
let r;
{
let x = 5;
r = &x;
}
println!("r: {r}");
}
$ cargo buildWhat is E0597?
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:

after the closing brace: } println!("r: {r}");
STACKr: &i32address of xdangling: x is gonex: i32 (dropped)5
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:

Rustsrc/main.rsRun ↗
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");
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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:?}");
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
fn add_bonus(scores: &Vec<i32>) {
scores.push(100);
}
fn main() {
let scores = vec![30, 95, 60];
add_bonus(&scores);
println!("{scores:?}");
}
$ cargo buildWhat is E0596?
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.

Starterexercises/count-words.rs
/// 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
One solutionexercises/count-words.solution.rsRun ↗
/// 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");
}
$ cargo run
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.

Starterexercises/count-matches.rs
/// 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
One solutionexercises/count-matches.solution.rsRun ↗
/// 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");
}
$ cargo run
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.

Starterexercises/report.rs
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
One solutionexercises/report.solution.rsRun ↗
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");
}
$ cargo run
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:

  1. A reference must never outlive the value it points to. (E0597, and E0515 when a function returns a reference to its own local.)
  2. 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:

Rustsrc/main.rs✗ won't compile
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}");
}
$ cargo buildWhat is E0505?
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.

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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.

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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.)

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
show: Ada
show: Ada
r and r2 point at the same String: true
moved: Ada

Borrowing in other languages

Read more