Ownership and borrowing
Goal: this is the most important lesson of the course. By the end of it you will understand where data lives (the stack and the heap), what it means for a value to have an owner, what a move is, how Copy differs from Clone, how the references & and &mut work, and what the borrowing rules are. You will also be able to read and fix the most common "borrow checker" errors: E0382, E0499, E0502, E0106, E0597 and E0507.
Don't worry if it does not all "click" the first time. Ownership is a concept that popular languages do not have, and everyone learning Rust goes through a stage of fighting the compiler. Come back to this lesson whenever you meet an error from this family in later ones.
Why all this?
Every program has to manage memory somehow: allocate it for data and free it when it is no longer needed. There are three main approaches:
- Manual management (C): you call
mallocandfreeyourself. It is fast, but easy to get wrong: a forgottenfreeis a memory leak, and a doublefreeor use of freed memory is a crash or a security hole. - A garbage collector (Python, JavaScript, Java, Go): the runtime periodically looks for unused data and frees it. It is convenient and safe, but costs CPU time and memory, and the moment of freeing is unpredictable.
- Ownership (Rust): based on a few simple rules, the compiler knows at which point in the code a value stops being needed, and inserts the freeing there itself. There is no garbage collector and no manual
free, and C-style bugs are caught at compile time.
The price is that you have to write code that follows those rules. Let's get to know them.
The stack and the heap
To understand ownership you need to know where data is stored. While running, a program uses two areas of memory.
The stack holds a function's local variables. It works like a stack of plates: when a function is called, a "frame" with its variables goes on top, and when the function ends, the whole frame is removed. This is very fast, but only data whose size is known at compile time can go on the stack: numbers, bool, char, tuples and fixed-length arrays.
The heap is for data whose size we only learn while the program is running, or which can grow, for example text typed in by a user or a list to which we keep adding elements. The program asks the system for a block of memory of the right size and gets a pointer (an address) to it. The pointer itself has a fixed size, so it can live on the stack.
A good example is the String type, that is, text that can grow. A variable of type String is three numbers on the stack: a pointer to the data on the heap, a length and a capacity. The characters themselves are on the heap:
STACK HEAP
s1: ┌──────────┬─────┐
│ pointer │ ───┼──────► ┌───┬───┬───┬───┬───┐
├──────────┼─────┤ │ h │ e │ l │ l │ o │
│ length │ 5 │ └───┴───┴───┴───┴───┘
├──────────┼─────┤
│ capacity │ 5 │
└──────────┴─────┘
Someone has to free that block on the heap eventually. This is exactly where ownership comes in.
The three ownership rules
- Every value in Rust has an owner: the variable it belongs to.
- At any given time a value has exactly one owner.
- When the owner goes out of scope, the value is dropped and the memory it occupied is freed.
A variable's scope is usually the block in curly braces where it was declared:
fn main() {
let a = String::from("outer");
{
let b = String::from("inner");
println!("{a}, {b}");
} // the scope of b ends here: the text "inner" is dropped from memory
println!("{a}");
} // the scope of a ends here: the text "outer" is dropped
outer, inner
outer
At every closing brace, the compiler inserts a call that frees the memory of the variables whose life ends there. This works much like a destructor in C++ (the RAII pattern). We can see it by defining a type that prints a message when it is dropped. Don't worry about the struct and impl Drop syntax, you will meet it in Lesson 6; for now only the order of the messages matters:
struct Noisy {
name: &'static str,
}
impl Drop for Noisy {
fn drop(&mut self) {
println!("dropping {}", self.name);
}
}
fn main() {
let _x = Noisy { name: "x" };
{
let _y = Noisy { name: "y" };
println!("end of inner block");
}
let _z = Noisy { name: "z" };
println!("end of main");
}
end of inner block
dropping y
end of main
dropping z
dropping x
You can see that y was dropped at the end of its block, and x and z at the end of main, in the reverse order of creation.
Moving ownership (move)
What happens when you assign one String variable to another?
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s2}");
}
hello
Only the part on the stack is copied (the pointer, the length and the capacity). The data on the heap is not copied, so both pointers would point to the same block:
s1: [pointer | 5 | 5] ──┐ (s1 is no longer valid)
├──► "hello" on the heap
s2: [pointer | 5 | 5] ──┘
If both variables were valid, at the end of main both would try to free the same block of memory. That is the classic "double free" bug from C. So after let s2 = s1; Rust considers that ownership has moved from s1 to s2, and s1 is no longer valid. We say the value has been moved. In Python or JS, after such an assignment both variables would point to the same object; in Rust there is only one owner.
Try using s1 after the move (deliberately broken code):
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{s1} and {s2}");
}
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:4:16
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 | println!("{s1} and {s2}");
| ^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let s2 = s1.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
This is E0382, the most common ownership error. Let's read it carefully:
borrow of moved value: s1: you are trying to use (here, to borrow for printing) a value that has been moved.- The label at line 2,
move occurs because s1 has type String, which does not implement the Copy trait, says why the move happened: theStringtype is not copied automatically (more onCopyshortly). - The label at line 3,
value moved here: this is where the move happened. - The label at line 4,
value borrowed here after move: and this is the forbidden use. help: consider cloning the value if the performance cost is acceptable: one possible fix is to clone the value, if the cost of copying is acceptable.
Borrow checker messages almost always tell a story like this in several steps: "something happened to the value here, and here you are trying to do something that conflicts with it". Look for the labels on the following lines of code.
How do you fix it? That depends on what you need: if both variables should have their own copy of the text, clone it; if the second variable only needs to "have a look" at the text, use a reference (coming up).
Copy and Clone
There is no such problem with numbers:
fn main() {
let a = 5;
let b = a; // a copy: a is still valid
println!("a = {a}, b = {b}");
}
a = 5, b = 5
Types whose values live entirely on the stack and can be cheaply copied bit by bit have the Copy trait. On assignment or when passed to a function they are copied, and the original stays valid. These types are Copy:
- all integer and floating-point types,
bool,char, - tuples and arrays, if all their elements are
Copy(e.g.(i32, f64)yes,(i32, String)no), - immutable references
&T(more on them shortly): this is why text written aslet t = "abc";(type&str) can be freely assigned onwards.
String, Vec and other types that manage memory on the heap are not Copy. If you really need an independent copy, call the clone() method explicitly. It also copies the data on the heap (a so-called deep copy):
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // a new block on the heap with a copy of the text
println!("s1 = {s1}, s2 = {s2}");
}
s1 = hello, s2 = hello
The difference between them:
Copy |
Clone |
|
|---|---|---|
| When it happens | automatically, on every assignment and pass | only when you explicitly call .clone() |
| Cost | always cheap (a copy of a few bytes) | can be expensive (e.g. a copy of a large text) |
| Examples | i32, f64, bool, char, &str |
String, Vec<T> and almost all types |
clone() is visible in the code on purpose: you can see at a glance that data is being copied at that spot. While you are learning, feel free to use clone() when you cannot cope with an ownership error. It is perfectly valid code, just sometimes less efficient. Over time you will learn to replace it with references.
Ownership and functions
Passing a value to a function works like assignment: Copy types are copied, and the rest are moved into the function's parameter. The function becomes their owner and drops them at its end (deliberately broken code):
fn show(text: String) {
println!("{text}");
} // text is dropped here
fn main() {
let s = String::from("Rust");
show(s); // ownership of s moves into the function
show(s); // error: s has already been moved
}
error[E0382]: use of moved value: `s`
--> src/main.rs:8:10
|
6 | let s = String::from("Rust");
| - move occurs because `s` has type `String`, which does not implement the `Copy` trait
7 | show(s); // ownership of s moves into the function
| - value moved here
8 | show(s); // error: s has already been moved
| ^ value used here after move
|
note: consider changing this parameter type in function `show` to borrow instead if owning the value isn't necessary
--> src/main.rs:1:15
|
1 | fn show(text: String) {
| ---- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
7 | show(s.clone()); // ownership of s moves into the function
| ++++++++
For more information about this error, try `rustc --explain E0382`.
This time the message is even more helpful: in the note: it points to the parameter of the show function and suggests changing its type so that the function borrows the value instead of taking it over ("consider changing this parameter type ... to borrow instead").
A function can also hand ownership back by returning a value:
fn make_greeting(name: &str) -> String {
let mut s = String::from("Hello, ");
s.push_str(name);
s // ownership passes to the caller
}
fn main() {
let greeting = make_greeting("Ola");
println!("{greeting}");
}
Hello, Ola
But if every function that only wants to read some text had to take it over and give it back, writing code would be exhausting. That is why references exist.
References and borrowing
A reference is a "pointer with guarantees": it lets you use a value without taking ownership of it. You create one with the & operator, and creating one is called borrowing. Just as in real life: you can use a borrowed thing, but you have to give it back, and the owner is still the owner.
fn length_of(s: &String) -> usize {
s.len()
} // s is only a reference, so nothing is dropped here
fn main() {
let text = String::from("café");
let n = length_of(&text); // we lend text to the function
println!("'{text}' is {n} bytes long"); // text still belongs to us
}
'café' is 5 bytes long
(The text has 4 letters but 5 bytes, because é takes two bytes in UTF-8; more on that in Lesson 5. There you will also see that the parameter is better written as &str instead of &String, and cargo clippy will point that out too.)
In memory, a reference is a pointer to the owner variable:
s (in length_of): [pointer] ──► text: [pointer | 5 | 5] ──► "café" on the heap
References created with & are immutable: you can read through them but not change anything (deliberately broken code):
fn append(s: &String) {
s.push_str("!");
}
fn main() {
let text = String::from("hey");
append(&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 append(s: &mut String) {
| +++
For more information about this error, try `rustc --explain E0596`.
E0596: cannot borrow *s as mutable, as it is behind a & reference: you cannot borrow *s (the value s points to) for modification, because you only have an ordinary & reference. The hint is ready: change the type to &mut String.
Mutable references: &mut
For a function to change a borrowed value, you need a mutable reference, &mut. Notice that mut appears in three places: on the variable (because it will be changed), on the borrow (&mut text) and in the parameter type (&mut String):
fn append(s: &mut String) {
s.push_str(", world");
}
fn increase(x: &mut i32) {
*x += 1; // * is dereferencing: "the value x points to"
}
fn main() {
let mut text = String::from("hello");
append(&mut text);
println!("{text}");
let mut counter = 0;
increase(&mut counter);
increase(&mut counter);
println!("counter = {counter}");
}
hello, world
counter = 2
The * operator (dereference) means "the value at this address". With a number you have to write it explicitly (*x += 1), because x is a reference, not a number. When calling methods (s.push_str(...)), Rust dereferences automatically, so you do not see the asterisk.
The same rules apply outside functions too: you can also keep a reference in a variable: let r = &text; or let r = &mut text;.
The borrowing rules
The borrow checker, the part of the compiler that watches over borrowing, enforces two rules:
- At any moment you can have either one mutable reference (
&mut) or any number of immutable ones (&), never both kinds at once. - A reference cannot outlive the value it points to.
The first rule is easy to remember as "many readers or one writer". Why is it needed? If someone changes data while someone else is reading it, the reader may see the data half-changed or, as you will see in a moment, a pointer to memory that no longer exists. In multithreaded programs that is a recipe for a data race. Rust rules out such situations at compile time.
The second rule guarantees that a reference never points to freed memory, that is, that there are no "dangling pointers" of the kind known from C.
E0499: two mutable references at once
Deliberately broken code:
fn main() {
let mut s = String::from("text");
let r1 = &mut s;
let r2 = &mut s;
r1.push_str(" first");
r2.push_str(" second");
println!("{s}");
}
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:4:14
|
3 | let r1 = &mut s;
| ------ first mutable borrow occurs here
4 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
5 | r1.push_str(" first");
| -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
How to read it: first mutable borrow occurs here (line 3) is the first mutable borrow; second mutable borrow occurs here (line 4) is the second, forbidden because the first is still in effect; first borrow later used here (line 5) is the proof that the first borrow is still in effect, because r1 is still used. That third label is the key: a borrow lasts until the last use of the reference, not until the end of the block.
The fix: make sure the borrows do not overlap. Since r1 is not needed after line 5, it is enough to change the order:
fn main() {
let mut s = String::from("text");
let r1 = &mut s;
r1.push_str(" first"); // last use of r1: the borrow ends
let r2 = &mut s; // now we can borrow again
r2.push_str(" second");
println!("{s}");
}
text first second
E0502: an ordinary and a mutable reference at once
This is a very practical example. We use a Vec here (a growable array, details in Lesson 5; the vec![...] macro creates a vector from the given elements): we take a reference to the first element and then add a new element (deliberately broken code):
fn main() {
let mut numbers = vec![1, 2, 3];
let first = &numbers[0];
numbers.push(4);
println!("first: {first}");
}
error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &numbers[0];
| ------- immutable borrow occurs here
4 | numbers.push(4);
| ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("first: {first}");
| ------- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
The message: you cannot borrow numbers as mutable (because push needs &mut) when it is already borrowed as immutable (first) and that immutable reference is used later (println!).
Why is this not pedantry? A vector keeps its elements in a block on the heap. When it runs out of room, push allocates a new, larger block, moves the elements there and frees the old one. The first reference would then point to freed memory. In C++ such code compiles and works "randomly"; Rust rejects it.
Fixes: use the reference before changing the vector, or copy the value (i32 is Copy):
fn main() {
let mut numbers = vec![1, 2, 3];
let first = &numbers[0];
println!("first: {first}"); // last use of the reference
numbers.push(4); // now changing it is allowed
let first = numbers[0]; // a copy of the value, not a reference
numbers.push(5);
println!("first: {first}, all: {numbers:?}");
}
first: 1
first: 1, all: [1, 2, 3, 4, 5]
Dangling references: E0106 and E0597
In C it is easy to return from a function a pointer to a local variable that no longer exists once the function ends. Let's try that in Rust (deliberately broken code):
fn create() -> &String {
let s = String::from("temporary");
&s
}
fn main() {
let r = create();
println!("{r}");
}
error[E0106]: missing lifetime specifier
--> src/main.rs:1:16
|
1 | fn create() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
1 | fn create() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
1 - fn create() -> &String {
1 + fn create() -> String {
|
For more information about this error, try `rustc --explain E0106`.
E0106: missing lifetime specifier: the lifetime is missing. A lifetime is the information about how long a reference is valid. Usually the compiler works it out itself, but here it has nothing to go on: the function takes no reference, so the returned reference cannot point to anything that will outlive the function ("there is no value for it to be borrowed from"). Everything created inside the function is dropped at its end.
The compiler suggests two things. The first, the 'static lifetime, it itself describes as uncommon, and here it really will not help. The second is the right one: return an owned value, not a reference. Even if you added a lifetime, the compiler would report another error, E0515: cannot return reference to local variable s: you are returning a reference to data owned by the current function, that is, data that is about to be dropped.
The fix: we return a String, and ownership simply passes to the caller:
fn create() -> String {
String::from("temporary")
}
fn main() {
let r = create();
println!("{r}");
}
temporary
Explicit lifetimes (the 'a syntax) go beyond this course. To begin with, one rule is enough: a function can return a reference only to something it received from outside (through a parameter), not to its own local variables.
The second borrowing rule also works inside a single function. A reference cannot outlive the value it points to (deliberately broken code):
fn main() {
let r;
{
let x = 5;
r = &x;
} // x stops existing
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 stops existing
| - `x` dropped here while still borrowed
7 | println!("r = {r}");
| - borrow later used here
For more information about this error, try `rustc --explain E0597`.
E0597: x does not live long enough. The labels say it all: x was borrowed here, x was dropped here (dropped here while still borrowed), and the reference is still used here.
E0507: cannot move out of borrowed content
This error often appears when working with collections. You want to take an element from a vector of strings (deliberately broken code):
fn main() {
let names = vec![String::from("Ola"), String::from("Jan")];
let first = names[0];
println!("{first}");
}
error[E0507]: cannot move out of index of `Vec<String>`
--> src/main.rs:3:17
|
3 | let first = names[0];
| ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
|
help: consider borrowing here
|
3 | let first = &names[0];
| +
help: consider cloning the value if the performance cost is acceptable
|
3 | let first = names[0].clone();
| ++++++++
For more information about this error, try `rustc --explain E0507`.
let first = names[0]; would try to move the string out of the vector into the variable, and that would leave a "hole" in the vector. Rust does not allow that. The compiler suggests two ways out: borrow the element (&names[0]) or clone it (names[0].clone()). With numbers the problem does not arise, because they are Copy; that is why let first = numbers[0]; worked in the previous example.
How to approach ownership errors
When the compiler rejects code with an ownership or borrowing error:
- Read the labels in order. Find the place where the value was moved or borrowed, the place of the conflict, and the place where the first borrow is still used.
- Ask yourself whether this code really needs ownership. If a function only reads data, let it take
&T. If it changes the data,&mut T. Taking ownership (T) is needed when the function is to keep the value for longer (e.g. store it in a struct) or to "consume" it. - Shorten the borrow. Often it is enough to change the order of statements so that the reference stops being used before the change happens.
- As a last resort, clone.
clone()is an honest solution, especially at the start of your learning.
A summary compared with other languages:
| Python / JavaScript | C | Rust | |
|---|---|---|---|
| Who frees memory | the garbage collector, at an undetermined moment | the programmer (free) |
the compiler inserts the freeing at the end of the owner's scope |
b = a for an object |
both names point to the same object | a copy of the pointer, two "owning" pointers | a move: a stops being valid (unless the type is Copy) |
| Dangling pointer | impossible | possible, a runtime bug | impossible, a compile error |
| Changing data someone is reading | allowed | allowed | forbidden by the borrowing rules |
Exercises
-
Fix this program in two ways: once using
clone(), once using a reference.Rustfn main() { let a = String::from("Rust"); let b = a; println!("{a} and {b}"); } -
Change the
showfunction so that it can be called twice with the same variable:Rustfn show(text: String) { println!("{text}"); } fn main() { let s = String::from("Rust"); show(s); show(s); } -
Write a function
add_exclamationthat takes a&mut Stringand appends!at the end. Call it twice on the same string and print the result. -
Fix the program (in two ways):
Rustfn main() { let mut numbers = vec![10, 20, 30]; let last = &numbers[2]; numbers.push(40); println!("{last}"); } -
Without compiling, answer: which of these variables (
x,s,tup,pair) are still valid after the assignments? Then check with the compiler.Rustlet x = 5; let y = x; let s = String::from("a"); let t = s; let tup = (1, 2.5); let t2 = tup; let pair = (1, String::from("a")); let p2 = pair;
Solutions
Exercise 1:
fn main() {
// way 1: a clone, two independent strings
let a = String::from("Rust");
let b = a.clone();
println!("{a} and {b}");
// way 2: a reference, d only borrows the string from c
let c = String::from("Rust");
let d = &c;
println!("{c} and {d}");
}
Rust and Rust
Rust and Rust
Exercise 2: the function borrows the string instead of taking it over:
fn show(text: &str) {
println!("{text}");
}
fn main() {
let s = String::from("Rust");
show(&s);
show(&s);
}
Rust
Rust
We used &str, not &String. Rust converts a &String reference to &str by itself, and a function like this will also accept a plain literal (show("abc")). Details in Lesson 5.
Exercise 3:
fn add_exclamation(s: &mut String) {
s.push('!');
}
fn main() {
let mut text = String::from("Warning");
add_exclamation(&mut text);
add_exclamation(&mut text);
println!("{text}");
}
Warning!!
(push adds a single char, push_str a string.)
Exercise 4:
fn main() {
// way 1: use the reference before changing the vector
let mut numbers = vec![10, 20, 30];
let last = &numbers[2];
println!("{last}");
numbers.push(40);
// way 2: copy the value (i32 is Copy)
let mut numbers2 = vec![10, 20, 30];
let last2 = numbers2[2];
numbers2.push(40);
println!("{last2} {numbers:?} {numbers2:?}");
}
30
30 [10, 20, 30, 40] [10, 20, 30, 40]
Exercise 5: x (a number is Copy) and tup (a tuple of numbers is Copy) stay valid. s and pair were moved: pair contains a String, so the whole tuple is not Copy. Using pair after the assignment gives an error:
fn main() {
let tup = (1, 2.5);
let t2 = tup;
let pair = (1, String::from("a"));
let p2 = pair;
println!("{tup:?} {t2:?} {p2:?}"); // fine
println!("{pair:?}"); // error
}
error[E0382]: borrow of moved value: `pair`
--> src/main.rs:7:16
|
4 | let pair = (1, String::from("a"));
| ---- move occurs because `pair` has type `(i32, String)`, which does not implement the `Copy` trait
5 | let p2 = pair;
| ---- value moved here
6 | println!("{tup:?} {t2:?} {p2:?}"); // fine
7 | println!("{pair:?}"); // error
| ^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
5 | let p2 = pair.clone();
| ++++++++
For more information about this error, try `rustc --explain E0382`.
Summary
- Fixed-size data lives on the stack; variable-size data (
String,Vec) lives on the heap, with only a pointer to it on the stack. - Every value has one owner; when the owner goes out of scope, the value is dropped.
- Assignment and passing to a function move the value, unless the type is
Copy. You make an independent copy explicitly withclone(). &Tborrows for reading,&mut Tfor changing. At one time there can be one&mutor many&, and a reference cannot outlive the value.- A borrow lasts until the last use of the reference: often it is enough to change the order of statements.