Skip to content
Learn Rust
Theme

Ownership and Moves

Ownership · Lesson 17

This is the big one. Every value in Rust has exactly one owner. When the owner goes away, the value is freed. Assigning a value to another variable moves it: the new variable becomes the owner, and the old one can't be used.

Coming from GoIn Go, b := a makes two slices share one array. In Rust, let b = a; moves the Vec to b and a can't be used any more, so if you want two you write a.clone().

Coming from KotlinIn Kotlin, val b = a gives two names for one list. In Rust it moves the list to b, a is no longer usable, and the list is freed when b goes out of scope instead of by a garbage collector.

Coming from PythonIn Python, b = a gives two names for one list. In Rust it moves the list, a can't be used again, and a.clone() is how you get a real second copy.

Coming from TypeScriptIn TypeScript, const b = a makes two references to one array. In Rust it moves the array to b, a can't be used any more, and the memory is freed when b goes out of scope, with no garbage collector.

Coming from JavaJava lets two variables share one object, and the garbage collector frees it later. In Rust let b = a; moves the Vec to b, and a can't be used again; the value is freed when b goes out of scope, with no GC.

Coming from C#In C#, var b = a; gives two references to one List. In Rust it moves the value, so a is no longer usable, and the memory is freed the moment its single owner goes out of scope instead of by a garbage collector.

Coming from COwnership is your "who calls free" rule, written into the types. After let b = a; the compiler forbids touching a, so a double free or a use after free can't compile.

Coming from C++let b = a; is auto b = std::move(a);, not auto b = a;: nothing is copied. And the compiler checks the move: using a afterwards is a compile error, not a read of a moved-from object. No destructor runs on the moved-from a either.

The quick comparison

Rustsrc/main.rsRun ↗
fn main() {
let a = vec![1, 2, 3];
let b = a; // the Vec MOVES to b. a can no longer be used.
println!("{b:?}");
let x = 5;
let y = x; // small values like numbers are copied instead
println!("{x} {y}");
}
$ cargo run
[1, 2, 3]
5 5
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := a // a and b now share the SAME array
b[0] = 100 // change through b...
fmt.Println(a) // ...and a changed too: [100 2 3]
fmt.Println(b)
x := 5
y := x
fmt.Println(x, y)
}
$ go run main.go
[100 2 3]
[100 2 3]
5 5
fun main() {
val a = mutableListOf(1, 2, 3)
val b = a // a and b point at the SAME list
b[0] = 100
println(a) // [100, 2, 3]
println(b)
val x = 5
val y = x
println("$x $y")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
[100, 2, 3]
[100, 2, 3]
5 5
a = [1, 2, 3]
b = a # no move: a and b are two names for the SAME list
print(b)
x = 5
y = x
print(f"{x} {y}")
$ python3 main.py
[1, 2, 3]
5 5
const a = [1, 2, 3];
const b = a; // b and a now point at the SAME array. Nothing moves.
console.log(b);
const x = 5;
const y = x; // numbers are copied
console.log(`${x} ${y}`);
$ tsc --strict --module nodenext --types node main.ts && node main.js
[ 1, 2, 3 ]
5 5
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
var a = new ArrayList<>(List.of(1, 2, 3));
var b = a; // a and b point at the SAME list
b.set(0, 100); // change through b...
System.out.println(a); // ...and a changed too
System.out.println(b);
int x = 5;
int y = x; // primitives like int are copied
System.out.println(x + " " + y);
}
}
$ javac Main.java && java Main
[100, 2, 3]
[100, 2, 3]
5 5
List<int> a = [1, 2, 3];
var b = a; // b points at the SAME list. a still works too.
Console.WriteLine($"[{string.Join(", ", b)}]");
var x = 5;
var y = x; // numbers are copied
Console.WriteLine($"{x} {y}");
$ dotnet run
[1, 2, 3]
5 5
#include <stdio.h>
#include <stdlib.h>
void print_ints(const int *xs, size_t len) {
printf("[");
for (size_t i = 0; i < len; i++) {
printf(i ? ", %d" : "%d", xs[i]);
}
printf("]\n");
}
int main(void) {
int *a = malloc(3 * sizeof(int));
a[0] = 1; a[1] = 2; a[2] = 3;
// C has no moves: b is a copy of the pointer, so a and b share one buffer.
// By convention b is now the owner, and we stop using a.
int *b = a;
a = NULL;
print_ints(b, 3);
int x = 5;
int y = x; // numbers are copied
printf("%d %d\n", x, y);
free(b); // the owner frees it, exactly once
return 0;
}
$ gcc -Wall -Wextra main.c -o main && ./main
[1, 2, 3]
5 5
#include <iostream>
#include <utility>
#include <vector>
void print(const std::vector<int>& xs) {
std::cout << "[";
for (std::size_t i = 0; i < xs.size(); ++i) {
std::cout << (i ? ", " : "") << xs[i];
}
std::cout << "]\n";
}
int main() {
std::vector<int> a{1, 2, 3};
std::vector<int> b = std::move(a); // move the buffer to b. Don't use a after this.
print(b);
int x = 5;
int y = x; // plain copy
std::cout << x << " " << y << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
[1, 2, 3]
5 5

What's different

  • A move does not copy the list. It copies the 24-byte header (pointer, length, capacity) and marks the old name as unusable. Moving a huge Vec is just as cheap as moving a tiny one.
  • Some types are copied instead of moved: numbers, bool, char, shared references &T, and tuples and arrays of those. They're called Copy. A plain copy of their bytes gives a complete, independent value, and there is nothing to free.
  • Why bother? Rust has no garbage collector. "Exactly one owner" is how it knows when to free memory, and that it frees it exactly once.
Gotcha

Using a variable after assigning it to another

Looks fine: in Go, Kotlin, Python, TypeScript, Java, C# and C++ it runs fine; in C it crashes at runtime. Rust won't compile it.

Fine in Go. Rust can't prove it's safe.Fine in Kotlin. Rust can't prove it's safe.Fine in Python. Rust can't prove it's safe.Fine in TypeScript. Rust can't prove it's safe.Fine in Java. Rust can't prove it's safe.Fine in C#. Rust can't prove it's safe.Broken in C. Rust catches it.Fine in C++. Rust can't prove it's safe.
Rustsrc/main.rs✗ won't compile
fn main() {
let a = vec![1, 2, 3];
let b = a;
println!("{a:?} {b:?}");
}
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := a
fmt.Println(a, b)
}
$ go run main.go✓ runs
[1 2 3] [1 2 3]
fun main() {
val a = mutableListOf(1, 2, 3)
val b = a
println("$a $b")
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
[1, 2, 3] [1, 2, 3]
a = [1, 2, 3]
b = a
print(a, b)
$ python3 main.py✓ runs
[1, 2, 3] [1, 2, 3]
const a = [1, 2, 3];
const b = a;
console.log(a, b);
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
[ 1, 2, 3 ] [ 1, 2, 3 ]
import java.util.List;
public class Main {
public static void main(String[] args) {
var a = List.of(1, 2, 3);
var b = a;
System.out.println(a + " " + b);
}
}
$ javac Main.java && java Main✓ runs
[1, 2, 3] [1, 2, 3]
List<int> a = [1, 2, 3];
var b = a;
Console.WriteLine($"[{string.Join(", ", a)}] [{string.Join(", ", b)}]");
$ dotnet run✓ runs
[1, 2, 3] [1, 2, 3]
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *a = malloc(3 * sizeof(int));
a[0] = 1; a[1] = 2; a[2] = 3;
int *b = a;
printf("[%d, %d, %d] [%d, %d, %d]\n", a[0], a[1], a[2], b[0], b[1], b[2]);
// clean up both variables
free(a);
free(b);
return 0;
}
$ gcc -fsanitize=address -g main.c -o main && ./main! crashes at runtime
[1, 2, 3] [1, 2, 3]
=================================================================
==1==ERROR: AddressSanitizer: attempting double-free on 0x············ in thread T0:
    #0 0x············ in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x············ in main main.c:12
    #2 0x············  (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
    #3 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
    #4 0x············ in _start (main+0xaac)

0x············ is located 0 bytes inside of 12-byte region [0x············,0x············)
freed by thread T0 here:
    #0 0x············ in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x············ in main main.c:11
    #2 0x············  (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
    #3 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
    #4 0x············ in _start (main+0xaac)

previously allocated by thread T0 here:
    #0 0x············ in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x············ in main main.c:5
    #2 0x············  (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
    #3 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
    #4 0x············ in _start (main+0xaac)

SUMMARY: AddressSanitizer: double-free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52 in free
==1==ABORTING
#include <iostream>
#include <vector>
void print(const std::vector<int>& xs) {
std::cout << "[";
for (std::size_t i = 0; i < xs.size(); ++i) {
std::cout << (i ? ", " : "") << xs[i];
}
std::cout << "]";
}
int main() {
std::vector<int> a{1, 2, 3};
auto b = a; // copies the whole vector
print(a);
std::cout << " ";
print(b);
std::cout << "\n";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
[1, 2, 3] [1, 2, 3]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:4:16
  |
2 |     let a = vec![1, 2, 3];
  |         - move occurs because `a` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     let b = a;
  |             - value moved here
4 |     println!("{a:?} {b:?}");
  |                ^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let b = a.clone();
  |              ++++++++
What happens in Go

Go runs this and prints [1 2 3] twice. a and b share one array, so a change through b would show up in a too.

What happens in Kotlin

Kotlin runs this and prints [1, 2, 3] twice. a and b are the same list, so a change through b would show up in a too.

What happens in Python

Python prints the list twice. b = a doesn't copy or move anything: both names point to the same list, so a change through b would show up in a too.

What happens in TypeScript

Prints the array twice. a and b are two names for the same array, so a change through b would show up in a too.

What happens in Java

Both names point at the same list, so it prints twice. A change through b would show up in a too, as the lesson program shows.

What happens in C#

Both lines print [1, 2, 3] because a and b are the same list. A change made through b would show up in a too.

What happens in C

C copies the pointer, so a and b share one buffer and both get freed. gcc builds it without a warning, and AddressSanitizer reports the double free.

What happens in C++

auto b = a; copies the whole vector, so it prints [1, 2, 3] twice. That's correct, but it allocates a second buffer. auto b = std::move(a); is what Rust's let b = a; does. It leaves a empty, but nothing stops you from reading a afterwards. clang-tidy's bugprone-use-after-move check catches many of those reads, but a lint isn't a guarantee.

What Rust sees

After let b = a, b owns the list. a is a name with nothing behind it.

Why Rust says no

Rust has no garbage collector, so it frees the list when its owner goes away. That only works if there is exactly one owner. With two, C would free the list twice (see the C version), and a garbage-collected language gives you two names for one list, so a change through b shows up in a. Rust's rule is one owner, and assignment hands it over. If you only want to look at the list through a second name, borrow it with &. That is the next lesson. The compiler's help: suggests a.clone(), which makes a second list; borrowing costs nothing.

The fix

Compiles and runs.

Rust, fixedsrc/main.rsRun ↗
fn main() {
let a = vec![1, 2, 3];
let b = &a; // borrow: look at a, don't take it. No copy.
println!("{a:?} {b:?}");
}
$ cargo run
[1, 2, 3] [1, 2, 3]

The toolkit

The three rules

Everything in this lesson follows from three rules:

  1. Every value has an owner: the variable, struct field or collection that holds it.
  2. There is one owner at a time. Handing a value to someone else moves it, and the old owner can’t use it any more.
  3. When the owner goes out of scope, the value is dropped: its destructor runs and its memory is freed, right there.

A scope is the block a variable was declared in, usually the nearest { … }. You can watch rule 3 happen. Drop is Rust’s destructor: give a type a drop method and Rust calls it when the value’s owner goes away. Here it just prints.

Rustsrc/main.rsRun ↗
// A type that says when it is freed. `Drop` is Rust's destructor: `drop` runs
// automatically when the owner goes away. You never call it yourself.
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
fn main() {
println!("start of main");
let _outer = Noisy("outer");
{
println!("start of block");
let _inner = Noisy("inner");
println!("end of block");
} // _inner's scope ends here, so it is dropped here
println!("end of main");
} // _outer is dropped here
$ cargo run
start of main
start of block
end of block
  drop inner
end of main
  drop outer

inner is freed when its block ends, outer when main ends. Nobody called free, and no garbage collector came along later: the compiler put each drop at the end of its scope, because by the rules nothing else can still be using the value. A file, a socket or a lock works the same way.

We’ll reuse Noisy below whenever the question is when something is freed.

Stack and heap

A program keeps its data in two places. The stack holds each function call’s local variables: it’s set aside when the function starts and given back when it returns, so it’s fast and needs no cleanup. Everything on it must have a size known when the program is compiled. (That’s the model to think in; the optimiser may keep a variable in a register or remove it.)

The heap is for everything else: data whose size is only known at runtime, like the items of a list or the text of a string, and data that must outlive the function that made it. The program asks for heap memory while it runs, and something has to give it back.

So a Vec or a String comes in two parts: a small, fixed-size header in the variable (a pointer to the data, a length and a capacity), and the data itself on the heap. Ownership is mostly about that second part: who gives the heap memory back, and when.

A variable holds the value

In Java, Kotlin, Python or JavaScript, a variable that holds a list holds a reference: an arrow to an object that lives somewhere else. b = a copies the arrow, and both names lead to the same object. C# works that way for classes.

In Rust a variable holds the value itself, the way a C# struct or a C local does. For a Vec, that value is the header. Go is in between: a slice variable holds its header by value, but b := a copies the header, so both point at one shared array.

So let b = a; has to do something with the value itself. It either copies it or moves it, and the type decides which.

Moves

Assigning a Vec, a String or any type that owns heap memory doesn’t copy it and doesn’t share it. It moves it: the new variable becomes the owner, and the old name is dead.

let a = vec![1, 2, 3];
STACKHEAPa: Vec<i32>ptrlen 3cap 3123
Before the move: a owns the Vec. On the stack it is three numbers: a pointer to the elements on the heap, the length and the capacity.
let b = a;
STACKHEAPa: Vec<i32> (moved)ptrlen 3cap 3unusable: the compiler stops any use of ab: Vec<i32>ptrlen 3cap 3123
After the move: the three header values were copied into b, and the elements on the heap stayed where they were. a's old bits may still be there, but the compiler won't let you use them, and it won't drop them. Only b will free the buffer.

This program checks that b really uses the buffer a had:

Rustsrc/main.rsRun ↗
fn main() {
let a = vec![1, 2, 3];
let a_buffer = a.as_ptr(); // where a's numbers live on the heap
let b = a; // move: b is the owner now. a can't be used again.
println!("b: {b:?}");
println!("b uses a's buffer: {}", b.as_ptr() == a_buffer);
}
$ cargo run
b: [1, 2, 3]
b uses a's buffer: true

Using a after the move is a compile error, not a crash and not a silent bug:

Rustsrc/main.rs✗ won't compile
fn main() {
let a = vec![1, 2, 3];
let b = a;
println!("b: {b:?}");
println!("a: {a:?}"); // a was moved into b
}
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:5:19
  |
2 |     let a = vec![1, 2, 3];
  |         - move occurs because `a` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     let b = a;
  |             - value moved here
4 |     println!("b: {b:?}");
5 |     println!("a: {a:?}"); // a was moved into b
  |                   ^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let b = a.clone();
  |              ++++++++

Read the error from the top: why a moves (“does not implement the Copy trait”), where it moved, and where you used it afterwards. The compiler suggests a.clone(), which works but is often not what you want. If you only need a second name to look at the list, borrow it with &a: that’s the next lesson.

Everywhere a value can move

A value also moves when you pass it to a function, return it, push it into a Vec, or put it in a struct field. Watch where each Noisy is dropped:

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
struct Order {
item: Noisy,
}
// Takes ownership. The value is dropped when this function ends,
// unless the function hands it on.
fn consume(n: Noisy) {
println!("consume got {}", n.0);
}
// Takes ownership and gives it back to the caller.
fn pass_back(n: Noisy) -> Noisy {
println!("pass_back got {}", n.0);
n
}
fn make() -> Noisy {
Noisy("made") // moves out to the caller; nothing is dropped
}
fn main() {
let a = Noisy("a");
consume(a); // a moves into the function...
println!("back in main"); // ...and was dropped before this line
let b = Noisy("b");
let b2 = pass_back(b); // b moved in, and back out as b2
println!("main owns {} again", b2.0);
let m = make();
println!("main owns {}", m.0);
let mut list = Vec::new();
list.push(Noisy("in vec")); // the Vec owns it now
let order = Order {
item: Noisy("in struct"),
}; // the struct owns it
println!("order holds {}", order.item.0);
println!("end of main");
}
$ cargo run
consume got a
  drop a
back in main
pass_back got b
main owns b again
main owns made
order holds in struct
end of main
  drop in struct
  drop in vec
  drop made
  drop b

consume(a) took a away, and the function was its last owner, so it was dropped when the function returned. pass_back and make returned their value, which is how a function gives ownership back. A Vec or struct owns what’s in it, and dropping the container drops its contents. At the end of main the four owners still alive are dropped, newest first (more below).

The function case catches everyone. A function that takes String takes the caller’s string away:

Rustsrc/main.rs✗ won't compile
fn count_words(text: String) -> usize {
text.split_whitespace().count()
}
fn main() {
let title = String::from("Ownership and Moves");
println!("words: {}", count_words(title));
println!("title: {title}");
}
$ cargo buildWhat is E0382?
error[E0382]: borrow of moved value: `title`
 --> src/main.rs:8:23
  |
6 |     let title = String::from("Ownership and Moves");
  |         ----- move occurs because `title` has type `String`, which does not implement the `Copy` trait
7 |     println!("words: {}", count_words(title));
  |                                       ----- value moved here
8 |     println!("title: {title}");
  |                       ^^^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `count_words` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:22
  |
1 | fn count_words(text: String) -> usize {
  |    -----------       ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     println!("words: {}", count_words(title.clone()));
  |                                            ++++++++

The compiler’s note: is the real fix: count_words only reads the text, so it should take &str (see functions). Loops catch people too, because the compiler counts every pass:

Rustsrc/main.rs✗ won't compile
fn main() {
let name = String::from("ana");
let mut guests = Vec::new();
for _ in 0..3 {
guests.push(name); // the first pass moves name away
}
println!("{guests:?}");
}
$ cargo buildWhat is E0382?
error[E0382]: use of moved value: `name`
 --> src/main.rs:5:21
  |
2 |     let name = String::from("ana");
  |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
3 |     let mut guests = Vec::new();
4 |     for _ in 0..3 {
  |     ------------- inside of this loop
5 |         guests.push(name); // the first pass moves name away
  |                     ^^^^ value moved here, in previous iteration of loop
  |
help: consider cloning the value if the performance cost is acceptable
  |
5 |         guests.push(name.clone()); // the first pass moves name away
  |                         ++++++++

The first pass moves name into the Vec, so the second has nothing to move. Here clone() is the right fix: you really do want three separate strings.

Copy types

Not everything moves. Some types are copied, and both names stay usable. A type can be Copy when a plain bit-for-bit copy of it gives a complete, independent value, and it has no Drop, so there’s nothing to clean up:

Rustsrc/main.rsRun ↗
// A small struct made only of Copy fields can be Copy too. Copy needs Clone.
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
fn main() {
// Numbers, bool and char are copied. Both names stay usable.
let n = 5;
let m = n;
let ok = true;
let ok2 = ok;
let c = 'é';
let c2 = c;
println!("n {n}, m {m}, ok {ok} {ok2}, c {c} {c2}");
// Tuples and arrays are Copy when everything in them is.
let pair = (1, 2.5);
let pair2 = pair;
let grid = [0u8; 4];
let grid2 = grid;
println!("pair {pair:?} {pair2:?}, grid {grid:?} {grid2:?}");
// Size doesn't matter: 1000 u64s is 8000 bytes, and still Copy.
let big = [7u64; 1000];
let big2 = big;
println!(
"big[999] {}, big2[999] {}, size {} bytes",
big[999],
big2[999],
size_of_val(&big)
);
// A shared reference is Copy: a second way to look, not a second value.
let text = String::from("shared");
let r1 = &text;
let r2 = r1;
println!("r1 {r1}, r2 {r2}");
// Your own type, thanks to #[derive(Clone, Copy)].
let p = Point { x: 1, y: 2 };
let mut q = p; // a copy
q.x = 10;
println!("p ({}, {}), q ({}, {})", p.x, p.y, q.x, q.y);
}
$ cargo run
n 5, m 5, ok true true, c é é
pair (1, 2.5) (1, 2.5), grid [0, 0, 0, 0] [0, 0, 0, 0]
big[999] 7, big2[999] 7, size 8000 bytes
r1 shared, r2 shared
p (1, 2), q (10, 2)
Type Copy? Why
integers, floats, bool, char yes plain bits
&T yes the value is somewhere else; copying the reference gives a second way to look at it, not a second value
tuples and arrays yes, if every element is size doesn’t matter: the 8,000-byte array above is Copy
your own struct or enum yes, with #[derive(Clone, Copy)] and all fields Copy you opt in
String, Vec, Box no copying the bytes would give two owners of one heap buffer
&mut T no two mutable references to one value is what mutable borrows forbid

The compiler refuses a Copy struct with a String field:

Rustsrc/main.rs✗ won't compile
#[derive(Clone, Copy)]
struct User {
id: u32,
name: String,
}
fn main() {
let u = User {
id: 1,
name: String::from("ana"),
};
println!("{} {}", u.id, u.name);
}
$ cargo buildWhat is E0204?
error[E0204]: the trait `Copy` cannot be implemented for this type
 --> src/main.rs:2:8
  |
1 | #[derive(Clone, Copy)]
  |                 ---- in this derive macro expansion
2 | struct User {
  |        ^^^^
3 |     id: u32,
4 |     name: String,
  |     ------------ this field does not implement `Copy`

Clone

clone() is the explicit copy. For a Vec or a String it allocates a new heap buffer and copies everything into it, so the two are fully independent:

let a = vec![1, 2, 3]; let mut b = a.clone(); b.push(4);
STACKHEAPa: Vec<i32>ptrlen 3cap 3b: Vec<i32>ptrlen 4cap 6a's buffer123b's buffer1234
Two owners, two buffers. Changing b can't change a. (b grew when 4 was pushed, so it has spare room; the exact capacity is up to Vec.)
Rustsrc/main.rsRun ↗
fn main() {
let a = vec![1, 2, 3];
let mut b = a.clone(); // a new heap buffer with copies of the elements
println!("different buffers: {}", a.as_ptr() != b.as_ptr());
b.push(4); // changing b leaves a alone
println!("a: {a:?}");
println!("b: {b:?}");
println!("a capacity {}, b capacity {}", a.capacity(), b.capacity());
// Cloning a Vec<String> clones each String too: String's clone copies its text.
let names = vec![String::from("ana"), String::from("bo")];
let copy = names.clone();
println!(
"names[0] and copy[0] share text: {}",
names[0].as_ptr() == copy[0].as_ptr()
);
}
$ cargo run
different buffers: true
a: [1, 2, 3]
b: [1, 2, 3, 4]
a capacity 3, b capacity 6
names[0] and copy[0] share text: false

How deep a clone goes depends on the type. #[derive(Clone)] writes clone for your types by cloning each field, so a clone goes exactly as deep as its fields’ own clone. A Vec<String> clones every String, which is why names[0] and copy[0] point at different text. An Rc’s clone, below, copies nothing but a pointer.

Cloning is often fine. Cloning a short string per request, or a small config at startup, costs nothing you’ll ever measure, and it’s better than contorting your code to avoid it. Take a second look when you clone to silence the compiler without knowing why it complained (a borrow would usually do), when you clone something large or inside a hot loop, or when you clone so two parts of the program can “update the same thing”. They won’t: they’ll update two copies. For shared state, see shared ownership.

Functions: own, borrow, or borrow mutably

A function’s parameter types say what it will do with your value:

The function… Parameter The caller writes Afterwards the caller…
only reads it &T (or &str, &[T]) f(&value) still has it
changes it in place &mut T f(&mut value) still has it, changed
keeps it, or consumes it T f(value) has given it away
Rustsrc/main.rsRun ↗
// Only reads: borrow with &. The caller keeps the Vec.
fn total(prices: &[u32]) -> u32 {
prices.iter().sum()
}
// Changes it in place: borrow mutably with &mut.
fn add_tax(prices: &mut Vec<u32>) {
for p in prices.iter_mut() {
*p += *p / 10;
}
}
// Keeps it: take ownership. The caller hands the Vec over for good.
struct Basket {
prices: Vec<u32>,
}
fn into_basket(prices: Vec<u32>) -> Basket {
Basket { prices }
}
fn main() {
let mut prices = vec![100, 250, 40];
println!("total: {}", total(&prices));
add_tax(&mut prices);
println!("after tax: {prices:?}");
let basket = into_basket(prices); // prices moves; we're done with it
println!("basket: {:?}", basket.prices);
}
$ cargo run
total: 390
after tax: [110, 275, 44]
basket: [110, 275, 44]

The strings lesson’s rule, “take &str, store String”, is this table applied to text. In general: borrow in parameters, own in struct fields and return values. Take ownership only when the function stores the value, returns it inside something else, or sends it to another thread. Borrowing has its own lessons: Borrowing and Mutable borrows.

Moving out of things

You can move a value out of a variable, but not out of the middle of a Vec. That would leave a hole: the Vec still owns that slot and would drop it again later.

Rustsrc/main.rs✗ won't compile
fn main() {
let names = vec![String::from("ana"), String::from("bo")];
let first = names[0]; // move a String out of the Vec?
println!("{first}");
}
$ cargo buildWhat is E0507?
error[E0507]: cannot move out of index of `Vec<String>`
 --> src/main.rs:3:17
  |
3 |     let first = names[0]; // move a String out of the Vec?
  |                 ^^^^^^^^ move occurs because value has type `String`, which does not implement the `Copy` trait
  |
help: consider borrowing here
  |
3 |     let first = &names[0]; // move a String out of the Vec?
  |                 +
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let first = names[0].clone(); // move a String out of the Vec?
  |                         ++++++++

E0507, “cannot move out of”, is the error you’ll get whenever you try to take something out of a place you don’t own outright: an index, a field behind a & or &mut, a value inside an Rc. The fixes are always one of these:

Rustsrc/main.rsRun ↗
use std::mem;
fn main() {
let mut names = vec![
String::from("ana"),
String::from("bo"),
String::from("cy"),
String::from("di"),
];
// 1. Copy it. names is untouched.
let a = names[0].clone();
println!("clone: {a:?}, names {names:?}");
// 2. Take it out. remove shifts the rest down; swap_remove fills the gap
// with the last item, which is faster.
let b = names.remove(0);
println!("remove(0): {b:?}, names {names:?}");
let c = names.swap_remove(0);
println!("swap_remove: {c:?}, names {names:?}");
// 3. Take it and leave an empty value in its place.
let d = mem::take(&mut names[0]);
println!("mem::take: {d:?}, names {names:?}");
// 4. Take it and leave a value you choose in its place.
let e = mem::replace(&mut names[1], String::from("new"));
println!("mem::replace: {e:?}, names {names:?}");
// 5. Option::take: take what's inside and leave None.
let mut slot = Some(String::from("token"));
let f = slot.take();
println!("Option::take: {f:?}, slot {slot:?}");
// 6. Done with the whole Vec? Loop over it by value and own each item.
for name in names {
println!("owned: {name:?}");
}
}
$ cargo run
clone: "ana", names ["ana", "bo", "cy", "di"]
remove(0): "ana", names ["bo", "cy", "di"]
swap_remove: "bo", names ["di", "cy"]
mem::take: "di", names ["", "cy"]
mem::replace: "cy", names ["", "new"]
Option::take: Some("token"), slot None
owned: ""
owned: "new"
You want Use What’s left behind
a copy, keep the original v[i].clone() the original
the item, and the Vec closes the gap v.remove(i) a shorter Vec (shifts everything after i)
the item, order doesn’t matter v.swap_remove(i) a shorter Vec (the last item fills the gap, no shifting)
the item, leave an empty one std::mem::take(&mut v[i]) the type’s Default: "", an empty Vec, 0, None
the item, leave one you choose std::mem::replace(&mut v[i], new) new
what’s in an Option opt.take() None
every item, done with the Vec for x in v nothing: the Vec is consumed
just to look at it &v[i] everything: nothing moved

mem::take and Option::take matter most inside methods that take &mut self. You can’t move a field out of self there, because self is only borrowed, but you can swap an empty value in and walk off with the old one. The first exercise needs exactly that.

Moving out of a struct

A struct you own outright is different: you can move single fields out of it. That’s a partial move:

Rustsrc/main.rsRun ↗
#[derive(Debug)]
struct User {
id: u32,
name: String,
email: String,
}
fn main() {
let user = User {
id: 7,
name: String::from("ana"),
email: String::from("[email protected]"),
};
let name = user.name; // move one field out
println!("moved out: {name}");
// The fields that weren't moved are still usable...
println!("still here: {} {}", user.id, user.email);
// ...but user as a whole isn't: `println!("{user:?}")` would not compile.
// Destructuring moves every field out at once, into new variables.
let other = User {
id: 8,
name: String::from("bo"),
email: String::from("[email protected]"),
};
let User { id, email, .. } = other;
println!("destructured: {id} {email}");
}
$ cargo run
moved out: ana
still here: 7 [email protected]
destructured: 8 [email protected]

After let name = user.name;, the other fields are still usable, but user as a whole isn’t. Destructuring with let User { id, email, .. } = other; moves fields out in one go, and is the tidy way to take a struct apart. One limit: you can’t move a field out of a type that has its own Drop, since its drop would then see a half-empty value. Clone the field, or use std::mem::take(&mut session.user), which leaves an empty String behind.

Rustsrc/main.rs✗ won't compile
struct Session {
user: String,
}
// A type with its own Drop can't be taken apart field by field: its drop()
// would run on a value with a hole in it.
impl Drop for Session {
fn drop(&mut self) {
println!("closing the session for {}", self.user);
}
}
fn main() {
let session = Session {
user: String::from("ana"),
};
let user = session.user;
println!("{user}");
}
$ cargo buildWhat is E0509?
error[E0509]: cannot move out of type `Session`, which implements the `Drop` trait
  --> src/main.rs:17:16
   |
17 |     let user = session.user;
   |                ^^^^^^^^^^^^
   |                |
   |                cannot move out of here
   |                move occurs because `session.user` has type `String`, which does not implement the `Copy` trait
   |
help: consider borrowing here
   |
17 |     let user = &session.user;
   |                +
help: consider cloning the value if the performance cost is acceptable
   |
17 |     let user = session.user.clone();
   |                            ++++++++

Scopes, drop order and let _

Values are dropped at the end of their scope, in reverse order of declaration: the newest first. That’s what you want, because something declared later may depend on something declared earlier. You can also end a value early.

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
struct Pair {
_first: Noisy,
_second: Noisy,
}
fn main() {
let _a = Noisy("a");
let _b = Noisy("b");
let c = Noisy("c");
drop(c); // ends c now, not at the end of main
println!("after drop(c)");
let mut d = Noisy("d (old)");
println!("d is {}, reassigning", d.0);
d = Noisy("d (new)"); // the old value is dropped right here
println!("d is now {}", d.0);
let _ = Noisy("unnamed"); // a new value that `_` doesn't bind: dropped at once
let _kept = Noisy("_kept"); // a name starting with _ does bind, and lives on
println!("after the two lets");
let _pair = Pair {
_first: Noisy("pair.first"),
_second: Noisy("pair.second"),
};
let _list = vec![Noisy("list[0]"), Noisy("list[1]")];
println!("end of main");
}
$ cargo run
  drop c
after drop(c)
d is d (old), reassigning
  drop d (old)
d is now d (new)
  drop unnamed
after the two lets
end of main
  drop list[0]
  drop list[1]
  drop pair.first
  drop pair.second
  drop _kept
  drop d (new)
  drop b
  drop a

drop(c) is an ordinary function that takes ownership and does nothing, so c dies at the end of it. Use it to release a lock or close a file early. Assigning a new value to d dropped the old one on the spot. At the end of main, variables went newest first, but inside the Vec and the struct, items were dropped first to last.

_ isn’t a variable; it binds nothing. So let _ = Noisy("unnamed");, a new value that nothing holds, was dropped at the end of that line. let _kept = … binds a real variable that just happens to start with _, and it lives to the end of the scope. The twist is let _ = x; with an existing variable:

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
fn main() {
println!("let _ = Noisy(…): a new value nobody holds");
let _ = Noisy("temporary"); // dropped right here, at the end of the statement
println!("let _ = x: an existing variable");
let x = Noisy("x");
let _ = x; // not a move and not a drop: x is untouched
println!("x is still usable: {}", x.0);
println!("let _kept = …");
let _kept = Noisy("kept"); // a real variable: lives to the end of main
println!("end of main");
}
$ cargo run
let _ = Noisy(…): a new value nobody holds
  drop temporary
let _ = x: an existing variable
x is still usable: x
let _kept = …
end of main
  drop kept
  drop x

That does nothing at all: it doesn’t move x and doesn’t drop it. Only a new value that nothing holds is dropped at once. That bit so many people holding locks, thinking let _ = mutex.lock() kept the lock, that the compiler refuses that line by default. It’s a lint, let_underscore_lock, that is set to deny:

Rustsrc/main.rs✗ won't compile
use std::sync::Mutex;
fn main() {
let counter = Mutex::new(0);
// Meant to hold the lock for the rest of the function. It doesn't.
let _ = counter.lock().unwrap();
println!("working while holding the lock (or so it seems)");
}
$ cargo build
error: non-binding let on a synchronization lock
 --> src/main.rs:6:9
  |
6 |     let _ = counter.lock().unwrap();
  |         ^ this lock is not assigned to a binding and is immediately dropped
  |
  = note: `#[deny(let_underscore_lock)]` (part of `#[deny(let_underscore)]`) on by default
help: consider binding to an unused variable to avoid immediately dropping the value
  |
6 |     let _unused = counter.lock().unwrap();
  |          ++++++
help: consider immediately dropping the value
  |
6 -     let _ = counter.lock().unwrap();
6 +     drop(counter.lock().unwrap());

One corner case: when a value is moved on only one path, as in an if, Rust keeps a hidden drop flag to remember whether it still owns the value, and drops it at the end of the scope only if so. A moved-from variable is not zeroed or emptied; Rust just stops you using it.

Rustsrc/main.rsRun ↗
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
fn consume(n: Noisy) {
println!("consumed {}", n.0);
}
fn run(give_away: bool) {
let n = Noisy(if give_away { "given away" } else { "kept" });
if give_away {
consume(n); // maybe moved...
}
println!("end of run({give_away})");
} // ...so Rust drops n here only if it is still here
fn main() {
run(true);
run(false);
}
$ cargo run
consumed given away
  drop given away
end of run(true)
end of run(false)
  drop kept

Going deeper

Why a move is cheap

Here are the sizes of a plain value, a Box and a Vec:

let n: u64 = 7; let boxed = Box::new([0u8; 4096]); let big = vec![7u64; 1_000_000]; let moved = big;
STACKHEAPn: u647boxed: Box<[u8; 4096]>ptrbig: Vec<u64> (moved)ptrlen 1000000cap 1000000moved: Vec<u64>ptrlen 1000000cap 10000004096 bytes000…1,000,000 × u64 = 8 MB777…
n is all on the stack. big is 24 bytes on the stack and 8 MB on the heap. Moving it to moved copied the 24 bytes; the 8 MB didn't move.
Rustsrc/main.rsRun ↗
fn main() {
let n: u64 = 7;
let boxed: Box<[u8; 4096]> = Box::new([0; 4096]);
let big: Vec<u64> = vec![7; 1_000_000];
// size_of_val(&x) is the size of x itself: the part on the stack.
println!(
"n: {} bytes on the stack, nothing on the heap",
size_of_val(&n)
);
println!(
"big: {} bytes on the stack, {} bytes on the heap",
size_of_val(&big),
big.len() * size_of::<u64>()
);
println!(
"boxed: {} bytes on the stack, {} bytes on the heap",
size_of_val(&boxed),
size_of_val(&*boxed)
);
let heap_before = big.as_ptr();
let moved = big; // copies the 24-byte header, not the 8 MB
println!(
"same heap data after the move: {}",
moved.as_ptr() == heap_before
);
println!("len {}, capacity {}", moved.len(), moved.capacity());
}
$ cargo run
n:     8 bytes on the stack, nothing on the heap
big:   24 bytes on the stack, 8000000 bytes on the heap
boxed: 8 bytes on the stack, 4096 bytes on the heap
same heap data after the move: true
len 1000000, capacity 1000000

A move costs the same whatever the size of the data: it copies a few machine words, and nothing is allocated or freed. That’s why passing a huge Vec to a function by value is fine.

Why Rust does this

Every language has to answer one question: when is it safe to free this memory? Rust’s answer is that the compiler knows each value’s one owner at every line, so it inserts the drop where the owner goes away. The memory is freed exactly once, at a known time, with no collector running. The price is the rules in this lesson, checked at compile time.

Who frees memory When b = a on a list What can go wrong
Rust the compiler inserts the drop when the owner goes out of scope moves it; a is unusable leaks are possible (an Rc cycle), but no double free or use-after-free in safe code
Go garbage collector some time after it’s unreachable shares the backing array aliasing; files closed by defer
Java, Kotlin garbage collector some time after it’s unreachable shares the object aliasing; resources need try-with-resources or use
C# garbage collector some time after it’s unreachable shares a class object, copies a struct aliasing; resources need using
Python reference counting, plus a cycle collector usually as soon as the last reference goes shares the object aliasing; cycles wait for the collector
TypeScript garbage collector some time after it’s unreachable shares the object aliasing
C you, with free when you call it copies the pointer: two owners double free, use-after-free, leaks
C++ destructors (RAII) when the owner goes out of scope deep-copies (auto b = a; copies a std::vector); std::move to move use-after-move compiles; dangling references

Aliasing means that after b = a, a change through b shows up in a. In C++ a move is opt-in, and using a moved-from object compiles: it’s in a “valid but unspecified state”, and its destructor still runs.

One honest note: ownership guarantees memory is freed at most once and never used after it’s freed. It doesn’t promise it’s freed at all. Leaking memory is safe in Rust: Box::leak and std::mem::forget exist on purpose, and an Rc cycle leaks just as it would with shared_ptr.

Box<T>: one value on the heap

Box::new(value) puts one value on the heap and gives you a single owner of it. When the box is dropped, the value inside is dropped and the memory freed. Use it for a recursive type (a node that holds the next node, which would otherwise have an infinite size), a value too big for the stack, or a trait object (Box<dyn Trait>, coming in Trait objects).

Rustsrc/main.rsRun ↗
// A list where each node holds the next one. Without Box, Node would have to
// contain itself, and its size would be infinite. A Box is one pointer.
enum List {
Node(i32, Box<List>),
End,
}
fn sum(list: &List) -> i32 {
match list {
List::Node(value, next) => value + sum(next),
List::End => 0,
}
}
fn main() {
let b = Box::new(41); // one i32, on the heap
let n = *b + 1; // * reads the value inside
println!("b {b}, n {n}");
let list = List::Node(1, Box::new(List::Node(2, Box::new(List::End))));
println!("sum of list: {}", sum(&list));
let moved = list; // moves the outer node; the heap nodes stay where they are
println!("size_of List: {} bytes", size_of_val(&moved));
} // moved is dropped, and with it every Box down the chain
$ cargo run
b 41, n 42
sum of list: 3
size_of List: 16 bytes

Shared ownership: Rc and Arc

Sometimes one owner isn’t enough: a graph, a cache, or a config shared by many components. Rc<T> is a reference-counted pointer: Rc::clone doesn’t copy the value, it adds an owner, and the value is freed when the last owner goes. Every owner gets shared, read-only access, like a &T. To change the value you put it in a RefCell (or a Mutex, across threads).

let first = Rc::new(Noisy("config")); let second = Rc::clone(&first);
STACKHEAPfirst: Rc<Noisy>ptrsecond: Rc<Noisy>ptrone allocation: the counts, then the valuestrong 2weakNoisy("config")
Two owners of one value. The count of owners (strong) lives on the heap next to the value. Dropping either owner lowers it; dropping the last one frees the value. The weak cell counts Weak pointers, which don't own; there are none here, and Rc::weak_count says 0.
Rustsrc/main.rsRun ↗
use std::rc::Rc;
struct Noisy(&'static str);
impl Drop for Noisy {
fn drop(&mut self) {
println!(" drop {}", self.0);
}
}
fn main() {
let first = Rc::new(Noisy("config"));
println!("owners: {}", Rc::strong_count(&first));
let second = Rc::clone(&first); // a second owner. Nothing is copied but a pointer.
println!("owners: {}", Rc::strong_count(&first));
println!("same value: {}", Rc::ptr_eq(&first, &second));
println!("weak_count: {}", Rc::weak_count(&first));
println!("size_of Rc<Noisy>: {} bytes", size_of::<Rc<Noisy>>());
drop(first);
println!("dropped first, owners: {}", Rc::strong_count(&second));
drop(second); // the last owner goes, so the value is freed
println!("end of main");
}
$ cargo run
owners: 1
owners: 2
same value: true
weak_count: 0
size_of Rc<Noisy>: 8 bytes
dropped first, owners: 1
  drop config
end of main

Rc is for one thread; the thread-safe version is Arc, covered in Shared state. Use them when the data really is shared, not to make the compiler stop complaining.

When ownership feels like a fight

The compiler sometimes rejects code that would be fine in your old language. The most common cause is holding a reference into something you’re changing:

Rustsrc/main.rs✗ won't compile
fn main() {
let mut scores = vec![10, 20, 30];
let best = &scores[2]; // a reference into the Vec
scores.push(40); // pushing may move the Vec's buffer
println!("best: {best}");
}
$ cargo buildWhat is E0502?
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let best = &scores[2]; // a reference into the Vec
  |                 ------ immutable borrow occurs here
4 |     scores.push(40); // pushing may move the Vec's buffer
  |     ^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("best: {best}");
  |                      ---- immutable borrow later used here

push may move the elements to a bigger buffer and free the old one, and best would then point at freed memory. In C and C++ that’s a real bug that compiles. In Go it’s a quieter one: after best := &s[2]; s = append(s, 40), best may point into the old array, so writes through it are lost. The borrowing lesson shows it with real output, in its Go box “Why bother, when Go has pointers?”. In Java, Kotlin, C#, Python or TypeScript the problem doesn’t come up: best = list.get(2) holds the element itself (or a copy of a number), not a slot in the list’s storage. The fixes:

Rustsrc/main.rsRun ↗
#[derive(Debug)]
struct Player {
name: String,
score: u32,
}
// Return an index, not a reference, when the caller will change the Vec next.
fn best_index(players: &[Player]) -> Option<usize> {
(0..players.len()).max_by_key(|&i| players[i].score)
}
// Return an owned value when you're building something new.
fn label(p: &Player) -> String {
format!("{} ({})", p.name, p.score)
}
fn main() {
let mut players = vec![
Player {
name: String::from("ana"),
score: 30,
},
Player {
name: String::from("bo"),
score: 50,
},
];
let best = best_index(&players).unwrap(); // a plain number: no borrow is held
players.push(Player {
name: String::from("cy"),
score: 10,
});
players[best].score += 5; // still the right player
println!("best: {}", label(&players[best]));
// Copy out the small thing you need, then change the original freely.
let top_score = players[best].score;
players.retain(|p| p.score * 2 >= top_score);
println!("kept: {players:?}");
}
$ cargo run
best: bo (55)
kept: [Player { name: "ana", score: 30 }, Player { name: "bo", score: 55 }]
  • Use an index instead of a reference. An index is a plain number; it doesn’t borrow anything. It’s also how people build graphs and trees in Rust: nodes in a Vec, links as indices.
  • Copy out the small thing you need (here, top_score) before changing the original.
  • Return owned values when a function builds something new; it can’t return a reference to its own local (returning references).
  • Restructure: read first and change second, or split a struct into parts you can borrow separately.

Wanting two owners. Ask who really owns the thing. Usually one struct does, and the other can keep an ID, an index or a key and look the value up when it needs it. If there’s honestly no single owner, that’s what Rc (or Arc) is for.

Stuck on an error you can’t fix? See Working with the Borrow Checker. Cloning to get unstuck is fine while you learn. What helps most is planning who owns each piece of data before you write the code: the owner holds T, everyone else borrows &T for as long as they need it.

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. Some checks compare heap addresses with as_ptr(), so a solution that clones instead of moving will fail.

Empty the inbox

Write two methods on Inbox. take_oldest removes and returns the first message, or None when there are none. take_all hands over every message and leaves the inbox empty but usable. Both take &mut self, so you can’t simply move self.unread out; see moving out of things.

Starterexercises/inbox.rs
struct Inbox {
unread: Vec<String>,
}
impl Inbox {
/// Remove and return the oldest (first) message, or None if there are none.
fn take_oldest(&mut self) -> Option<String> {
todo!("check is_empty, then remove(0)")
}
/// Hand over every message at once, leaving the inbox empty but usable.
fn take_all(&mut self) -> Vec<String> {
todo!("std::mem::take the Vec out of self")
}
}
fn main() {
let mut inbox = Inbox {
unread: vec![
String::from("hi"),
String::from("lunch?"),
String::from("bye"),
],
};
// Some checks compare heap addresses: the text should move, not be copied.
let hi_text = inbox.unread[0].as_ptr();
let oldest = inbox.take_oldest().unwrap();
assert_eq!(oldest, "hi");
assert_eq!(
oldest.as_ptr(),
hi_text,
"take_oldest should move, not clone"
);
assert_eq!(inbox.unread, ["lunch?", "bye"]);
let lunch_text = inbox.unread[0].as_ptr();
let all = inbox.take_all();
assert_eq!(all, ["lunch?", "bye"]);
assert_eq!(
all[0].as_ptr(),
lunch_text,
"take_all should move, not clone"
);
assert!(
inbox.unread.is_empty(),
"take_all must leave the inbox empty"
);
// An empty inbox has nothing to give, and doesn't panic.
assert_eq!(inbox.take_oldest(), None);
assert!(inbox.take_all().is_empty());
// And it still works afterwards.
inbox.unread.push(String::from("new"));
assert_eq!(inbox.take_oldest(), Some(String::from("new")));
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/inbox.solution.rsRun ↗
struct Inbox {
unread: Vec<String>,
}
impl Inbox {
/// Remove and return the oldest (first) message, or None if there are none.
fn take_oldest(&mut self) -> Option<String> {
if self.unread.is_empty() {
None
} else {
Some(self.unread.remove(0))
}
}
/// Hand over every message at once, leaving the inbox empty but usable.
fn take_all(&mut self) -> Vec<String> {
// You can't move a field out of `&mut self`. Take it, leaving an empty Vec.
std::mem::take(&mut self.unread)
}
}
fn main() {
let mut inbox = Inbox {
unread: vec![
String::from("hi"),
String::from("lunch?"),
String::from("bye"),
],
};
// Some checks compare heap addresses: the text should move, not be copied.
let hi_text = inbox.unread[0].as_ptr();
let oldest = inbox.take_oldest().unwrap();
assert_eq!(oldest, "hi");
assert_eq!(
oldest.as_ptr(),
hi_text,
"take_oldest should move, not clone"
);
assert_eq!(inbox.unread, ["lunch?", "bye"]);
let lunch_text = inbox.unread[0].as_ptr();
let all = inbox.take_all();
assert_eq!(all, ["lunch?", "bye"]);
assert_eq!(
all[0].as_ptr(),
lunch_text,
"take_all should move, not clone"
);
assert!(
inbox.unread.is_empty(),
"take_all must leave the inbox empty"
);
// An empty inbox has nothing to give, and doesn't panic.
assert_eq!(inbox.take_oldest(), None);
assert!(inbox.take_all().is_empty());
// And it still works afterwards.
inbox.unread.push(String::from("new"));
assert_eq!(inbox.take_oldest(), Some(String::from("new")));
println!("all checks passed");
}
$ cargo run
all checks passed

Sort words into two piles

Move each word into a “short” or a “long” list, keeping their order, without cloning a single String. A word is short if it has at most max characters. Watch out: "café" is four characters but five bytes.

Starterexercises/split-by-length.rs
/// Move each word into `short` (at most `max` characters) or `long`,
/// keeping their order. Don't clone: the caller has given you the words.
fn split_by_length(words: Vec<String>, max: usize) -> (Vec<String>, Vec<String>) {
todo!("loop over words by value; count chars, not bytes")
}
fn main() {
let words = vec![
String::from("ox"),
String::from("crab"),
String::from("café"),
String::from("ferris"),
];
let crab_text = words[1].as_ptr();
let ferris_text = words[3].as_ptr();
let (short, long) = split_by_length(words, 4);
// "café" is 5 bytes but 4 characters, so it is short.
assert_eq!(short, ["ox", "crab", "café"]);
assert_eq!(long, ["ferris"]);
// Same heap text as before: each String moved, none was copied.
assert_eq!(
short[1].as_ptr(),
crab_text,
"move the Strings, don't clone them"
);
assert_eq!(
long[0].as_ptr(),
ferris_text,
"move the Strings, don't clone them"
);
let (short, long) = split_by_length(Vec::new(), 4);
assert!(short.is_empty() && long.is_empty());
let (short, long) = split_by_length(vec![String::new()], 0);
assert_eq!(short, [""]);
assert!(long.is_empty());
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/split-by-length.solution.rsRun ↗
/// Move each word into `short` (at most `max` characters) or `long`,
/// keeping their order. Don't clone: the caller has given you the words.
fn split_by_length(words: Vec<String>, max: usize) -> (Vec<String>, Vec<String>) {
let mut short = Vec::new();
let mut long = Vec::new();
// Looping over `words` by value hands us each String to own.
for word in words {
if word.chars().count() <= max {
short.push(word);
} else {
long.push(word);
}
}
(short, long)
}
fn main() {
let words = vec![
String::from("ox"),
String::from("crab"),
String::from("café"),
String::from("ferris"),
];
let crab_text = words[1].as_ptr();
let ferris_text = words[3].as_ptr();
let (short, long) = split_by_length(words, 4);
// "café" is 5 bytes but 4 characters, so it is short.
assert_eq!(short, ["ox", "crab", "café"]);
assert_eq!(long, ["ferris"]);
// Same heap text as before: each String moved, none was copied.
assert_eq!(
short[1].as_ptr(),
crab_text,
"move the Strings, don't clone them"
);
assert_eq!(
long[0].as_ptr(),
ferris_text,
"move the Strings, don't clone them"
);
let (short, long) = split_by_length(Vec::new(), 4);
assert!(short.is_empty() && long.is_empty());
let (short, long) = split_by_length(vec![String::new()], 0);
assert_eq!(short, [""]);
assert!(long.is_empty());
println!("all checks passed");
}
$ cargo run
all checks passed

Hand over an item

Move one player’s item to another, and return whatever the receiver was holding. If the giver has nothing, nothing changes. Both players are &mut borrowed, so you’ll need to take the item out of an Option rather than move it. It fits in two lines.

Starterexercises/hand-over.rs
struct Player {
name: String,
item: Option<String>,
}
/// Move `from`'s item to `to`. Return what `to` was holding before, if anything.
/// If `from` has no item, change nothing and return None.
fn hand_over(from: &mut Player, to: &mut Player) -> Option<String> {
todo!("take the item out of from's Option, then swap it into to's")
}
fn main() {
let mut ana = Player {
name: String::from("ana"),
item: Some(String::from("sword")),
};
let mut bo = Player {
name: String::from("bo"),
item: None,
};
let sword_text = ana.item.as_ref().unwrap().as_ptr();
// ana gives bo her sword. bo had nothing, so nothing comes back.
assert_eq!(hand_over(&mut ana, &mut bo), None);
assert_eq!(ana.item, None);
assert_eq!(bo.item.as_deref(), Some("sword"));
assert_eq!(
bo.item.as_ref().unwrap().as_ptr(),
sword_text,
"move the item, don't clone it"
);
// ana has nothing to give: bo keeps his sword.
assert_eq!(hand_over(&mut ana, &mut bo), None);
assert_eq!(bo.item.as_deref(), Some("sword"));
// ana finds a shield and gives it to bo, who hands back what he was holding.
ana.item = Some(String::from("shield"));
let returned = hand_over(&mut ana, &mut bo);
assert_eq!(returned.as_deref(), Some("sword"));
assert_eq!(bo.item.as_deref(), Some("shield"));
assert_eq!(ana.item, None);
println!("{} and {}: all checks passed", ana.name, bo.name);
}

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/hand-over.solution.rsRun ↗
struct Player {
name: String,
item: Option<String>,
}
/// Move `from`'s item to `to`. Return what `to` was holding before, if anything.
/// If `from` has no item, change nothing and return None.
fn hand_over(from: &mut Player, to: &mut Player) -> Option<String> {
// take() moves the item out and leaves None. `?` returns early on None.
let item = from.item.take()?;
// replace() puts the new item in and gives back the old one.
to.item.replace(item)
}
fn main() {
let mut ana = Player {
name: String::from("ana"),
item: Some(String::from("sword")),
};
let mut bo = Player {
name: String::from("bo"),
item: None,
};
let sword_text = ana.item.as_ref().unwrap().as_ptr();
// ana gives bo her sword. bo had nothing, so nothing comes back.
assert_eq!(hand_over(&mut ana, &mut bo), None);
assert_eq!(ana.item, None);
assert_eq!(bo.item.as_deref(), Some("sword"));
assert_eq!(
bo.item.as_ref().unwrap().as_ptr(),
sword_text,
"move the item, don't clone it"
);
// ana has nothing to give: bo keeps his sword.
assert_eq!(hand_over(&mut ana, &mut bo), None);
assert_eq!(bo.item.as_deref(), Some("sword"));
// ana finds a shield and gives it to bo, who hands back what he was holding.
ana.item = Some(String::from("shield"));
let returned = hand_over(&mut ana, &mut bo);
assert_eq!(returned.as_deref(), Some("sword"));
assert_eq!(bo.item.as_deref(), Some("shield"));
assert_eq!(ana.item, None);
println!("{} and {}: all checks passed", ana.name, bo.name);
}
$ cargo run
ana and bo: all checks passed

Read more