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
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}");}[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)}[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")}[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 listprint(b)
x = 5y = xprint(f"{x} {y}")[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 copiedconsole.log(`${x} ${y}`);[ 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); }}[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 copiedConsole.WriteLine($"{x} {y}");[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;}[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";}[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 calledCopy. 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.
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.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)}[1 2 3] [1 2 3]
fun main() { val a = mutableListOf(1, 2, 3) val b = a println("$a $b")}[1, 2, 3] [1, 2, 3]
a = [1, 2, 3]b = aprint(a, b)[1, 2, 3] [1, 2, 3]
const a = [1, 2, 3];const b = a;console.log(a, b);[ 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); }}[1, 2, 3] [1, 2, 3]
List<int> a = [1, 2, 3];var b = a;Console.WriteLine($"[{string.Join(", ", a)}] [{string.Join(", ", b)}]");[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;}[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";}[1, 2, 3] [1, 2, 3]
What does the compiler say? Guess first, then open.
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(); | ++++++++
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.
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.
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.
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.
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.
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.
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.
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.
After let b = a, b owns the list. a is a name with nothing behind it.
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.
fn main() { let a = vec![1, 2, 3]; let b = &a; // borrow: look at a, don't take it. No copy. println!("{a:?} {b:?}");}[1, 2, 3] [1, 2, 3]
The toolkit
The three rules
Everything in this lesson follows from three rules:
- Every value has an owner: the variable, struct field or collection that holds it.
- 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.
- 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.
// 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 herestart 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.
a owns the Vec. On the stack it is three numbers: a pointer to the elements on the heap, the length and the capacity.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:
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);}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:
fn main() { let a = vec![1, 2, 3]; let b = a; println!("b: {b:?}"); println!("a: {a:?}"); // a was moved into b}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:
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");}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:
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}");}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:
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:?}");}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:
// 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);}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:
#[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);}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:
b can't change a. (b grew when 4 was pushed, so it has spare room; the exact capacity is up to Vec.)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() );}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 |
// 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);}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.
fn main() { let names = vec![String::from("ana"), String::from("bo")]; let first = names[0]; // move a String out of the Vec? println!("{first}");}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:
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:?}"); }}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:
#[derive(Debug)]struct User { id: u32, name: String, email: String,}
fn main() { let user = User { id: 7, name: String::from("ana"), };
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"), }; let User { id, email, .. } = other; println!("destructured: {id} {email}");}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.
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}");}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.
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");}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:
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");}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:
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)");}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.
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);}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:
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.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());}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).
// 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 chainb 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).
Weak pointers, which don't own; there are none here, and Rc::weak_count says 0.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");}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:
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}");}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:
#[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:?}");}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.
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
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");}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.
/// 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
/// 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");}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.
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
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);}ana and bo: all checks passed
Read more
- The Rust Book, chapter 4.1: What Is Ownership? The original explanation, with its own stack and heap diagrams.
- The
Droptrait, including the exact drop order rules. std::mem::take,std::mem::replaceandOption::take.CloneandCopy: what each means, and when a type can beCopy.BoxandRcin the standard library docs.- Rust by Example: Ownership and moves and Partial moves.
- The Rust Reference: Destructors, for the full rules on when things are dropped.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.