Guide contents

Lesson 11 of 94

Ownership: The Three Rules

Ownership is the idea that makes Rust different from every language you have used. It is how Rust frees memory without a garbage collector and without manual free() calls, and it is checked entirely at compile time, so it costs nothing at runtime.

This section of the course is long and example-heavy on purpose. If you understand these seven lessons, the rest of Rust follows.

Background: the stack and the heap

A program stores data in two places:

  • The stack holds local variables of fixed size. It is very fast, and each function's space is freed automatically when the function returns.
  • The heap holds data whose size can change or is not known at compile time, such as the text of a String or the elements of a Vec. Something has to decide when heap memory can be reused.

Rust has no garbage collector. Instead, the compiler tracks exactly one owner for every heap value and inserts the code to free the value at the point where its owner goes away.

The three rules

  1. Each value in Rust has an owner, a variable.
  2. There can only be one owner at a time.
  3. When the owner goes out of scope, the value is dropped: its memory is freed immediately.

The examples below show each rule, then what happens when you break it.

1. A variable is valid from its declaration to the end of its scope

Rust
fn main() {
    // `greeting` does not exist yet
    {
        let greeting = String::from("hello"); // `greeting` is valid from here
        println!("{greeting}");
    } // the scope ends: `greeting` is dropped and its heap memory is freed
    println!("greeting is gone");
}
Output
hello
greeting is gone

This is rule 3. The freeing happens at the closing brace, at a moment you can see in the source code, not "at some point later" as with a garbage collector.

2. What a String looks like in memory

A String has two parts. On the stack, a small fixed-size header with three numbers: a pointer to the text, the length, and the capacity. On the heap, the text itself.

Text
   stack (String)                     heap
 ┌──────────┬───────┐            ┌───┬───┬───┬───┬───┐
 │ ptr      │  ─────┼──────────► │ h │ e │ l │ l │ o │
 │ len      │ 5     │            └───┴───┴───┴───┴───┘
 │ capacity │ 5     │
 └──────────┴───────┘
Rust
fn main() {
    let mut s = String::from("hello");
    println!("len={} capacity={}", s.len(), s.capacity());
    s.push_str(", world");
    println!("len={} capacity={}", s.len(), s.capacity());
    println!("header size: {} bytes", std::mem::size_of::<String>());
}
Output
len=5 capacity=5
len=12 capacity=12
header size: 24 bytes

The header is always 24 bytes (three 8-byte numbers on a 64-bit machine), however long the text is. The capacity grows in jumps so that most pushes don't need a new allocation.

3. Assignment moves ownership

Here is the first big surprise.

Rust✗ does not compile · E0382
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{s1}, world!");
}
Output
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:4:16
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |     println!("{s1}, world!");
  |                ^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

let s2 = s1; moves the String from s1 to s2. After the move, s1 is no longer valid, and the compiler refuses any use of it.

4. Why a move, and not a copy?

After let s2 = s1;, Rust copies only the 24-byte header. The text on the heap is not copied. Both headers would point to the same heap text:

Text
 s1 ┌─────┐
    │ ptr ├──────┐          heap
    └─────┘      ├──────► "hello"
 s2 ┌─────┐      │
    │ ptr ├──────┘
    └─────┘

Now think about rule 3. When the scope ends, both s1 and s2 would be dropped, and the heap text would be freed twice: a double free, one of the classic C bugs that corrupts memory. Rust's fix is simple: after the copy, s1 is treated as no longer valid. Only s2 owns the text, so it is freed exactly once. That is a move.

This is rule 2 in action: one owner at a time.

5. The new owner works normally

Rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{s2}, world!");
}
Output
hello, world!

Moves are cheap: 24 bytes are copied, however large the text is. Rust never copies heap data implicitly. When you want a real copy, you ask for it with .clone(), covered in the next lesson.

6. Integers are copied, not moved

For types that live entirely on the stack, such as integers, copying is as cheap as a move and there is no heap data to free twice. So they are simply copied, and both variables stay valid.

Rust
fn main() {
    let x = 5;
    let y = x;
    println!("x = {x}, y = {y}");
}
Output
x = 5, y = 5

The exact rule is the Copy trait, explained in the next lesson.

7. The same Vec moves too

Vec has the same layout as String (pointer, length, capacity on the stack; elements on the heap), so it moves the same way.

Rust✗ does not compile · E0382
fn main() {
    let scores = vec![90, 85, 77];
    let backup = scores;
    println!("{:?}", scores);
}
Output
error[E0382]: borrow of moved value: `scores`
 --> src/main.rs:4:22
  |
2 |     let scores = vec![90, 85, 77];
  |         ------ move occurs because `scores` has type `Vec<i32>`, which does not implement the `Copy` trait
3 |     let backup = scores;
  |                  ------ value moved here
4 |     println!("{:?}", scores);
  |                      ^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let backup = scores.clone();
  |                        ++++++++

8. Pushing a value into a Vec moves it

The Vec becomes the new owner of the String.

Rust✗ does not compile · E0382
fn main() {
    let mut names = Vec::new();
    let name = String::from("Ferris");
    names.push(name);
    println!("added {name}");
}
Output
error[E0382]: borrow of moved value: `name`
 --> src/main.rs:5:22
  |
3 |     let name = String::from("Ferris");
  |         ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
4 |     names.push(name);
  |                ---- value moved here
5 |     println!("added {name}");
  |                      ^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
4 |     names.push(name.clone());
  |                    ++++++++

names now owns the text, and name is empty-handed. The fix is to use the value before moving it, or to read it back from the vector:

Rust
fn main() {
    let mut names = Vec::new();
    let name = String::from("Ferris");
    println!("adding {name}");
    names.push(name);
    println!("names[0] = {}", names[0]);
}
Output
adding Ferris
names[0] = Ferris

9. A Vec owns its elements

When the vector is dropped, it drops every element it owns. One owner at the top frees everything below it.

Rust
fn main() {
    let total_bytes;
    {
        let lines = vec![
            String::from("first line"),
            String::from("second line"),
            String::from("third line"),
        ];
        total_bytes = lines[0].len() + lines[1].len() + lines[2].len();
    } // `lines` is dropped here, and so are all three Strings
    println!("the lines used {total_bytes} bytes of text");
}
Output
the lines used 31 bytes of text

10. You cannot move an element out of a Vec by index

If let first = list[0]; moved the String out, the vector would be left with a hole: an invalid element it would later try to free. So Rust forbids it.

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

The next examples show the correct ways to take ownership of an element.

11. Taking ownership with remove and pop

Methods that take an element out of the vector hand you its ownership, and the vector no longer contains it.

Rust
fn main() {
    let mut tasks = vec![
        String::from("deploy"),
        String::from("test"),
        String::from("build"),
    ];
    let last = tasks.pop().unwrap();
    let first = tasks.remove(0);
    println!("took '{first}' and '{last}'");
    println!("left in the vector: {:?}", tasks);
}
Output
took 'deploy' and 'build'
left in the vector: ["test"]

pop() returns an Option because the vector might be empty; unwrap() takes the value out of it. See Option for the proper ways to handle that.

12. A for loop over a Vec moves the Vec

Rust✗ does not compile · E0382
fn main() {
    let words = vec![String::from("one"), String::from("two")];
    for word in words {
        println!("{word}");
    }
    println!("{}", words.len());
}
Output
error[E0382]: borrow of moved value: `words`
 --> src/main.rs:6:20
  |
2 |     let words = vec![String::from("one"), String::from("two")];
  |         ----- move occurs because `words` has type `Vec<String>`, which does not implement the `Copy` trait
3 |     for word in words {
  |                 ----- `words` moved due to this implicit call to `.into_iter()`
...
6 |     println!("{}", words.len());
  |                    ^^^^^ value borrowed here after move
  |
note: `into_iter` takes ownership of the receiver `self`, which moves `words`
 --> /rustc/<version>/library/core/src/iter/traits/collect.rs:310:17
help: consider iterating over a slice of the `Vec<String>`'s content to avoid moving into the `for` loop
  |
3 |     for word in &words {
  |                 +

for word in words gives the loop ownership of the vector, and each word owns one String for one iteration. Afterwards, everything has been dropped. You saw the fix, for word in &words, in A First Look at String & Vec. The & is borrowing, the topic of References & Borrowing.

13. Moving out inside a loop

The compiler notices a move that would happen again on the next iteration.

Rust✗ does not compile · E0382
fn main() {
    let report = String::from("quarterly numbers");
    let mut outbox = Vec::new();
    for _recipient in 0..3 {
        outbox.push(report);
    }
    println!("{}", outbox.len());
}
Output
error[E0382]: use of moved value: `report`
 --> src/main.rs:5:21
  |
2 |     let report = String::from("quarterly numbers");
  |         ------ move occurs because `report` has type `String`, which does not implement the `Copy` trait
3 |     let mut outbox = Vec::new();
4 |     for _recipient in 0..3 {
  |     ---------------------- inside of this loop
5 |         outbox.push(report);
  |                     ^^^^^^ value moved here, in previous iteration of loop
  |
help: consider cloning the value if the performance cost is acceptable
  |
5 |         outbox.push(report.clone());
  |                           ++++++++

The first iteration moves report into outbox, so there is nothing left to move on the second. To send three copies, you would clone it, as shown in the next lesson.

14. A move in only one branch still counts

If a value might have been moved, the compiler treats it as moved. It will not let you use a value that is valid on some paths and gone on others.

Rust✗ does not compile · E0382
fn main() {
    let urgent = true;
    let message = String::from("server down");
    let mut pager_queue = Vec::new();
    if urgent {
        pager_queue.push(message);
    }
    println!("logged: {message}");
}
Output
error[E0382]: borrow of moved value: `message`
 --> src/main.rs:8:24
  |
3 |     let message = String::from("server down");
  |         ------- move occurs because `message` has type `String`, which does not implement the `Copy` trait
...
6 |         pager_queue.push(message);
  |                          ------- value moved here
7 |     }
8 |     println!("logged: {message}");
  |                        ^^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
6 |         pager_queue.push(message.clone());
  |                                 ++++++++

This is exactly the kind of "valid only if some condition held" bug that Rust rules out at compile time.

15. Giving a moved-from variable a new value

A moved-from mut variable is not dead forever. You can assign it a fresh value, and it becomes usable again.

Rust
fn main() {
    let mut buffer = String::from("batch 1");
    let mut sent = Vec::new();

    sent.push(buffer);                  // buffer moved out
    buffer = String::from("batch 2");   // buffer owns a new value
    sent.push(buffer);
    buffer = String::from("batch 3");

    println!("sent: {:?}", sent);
    println!("current buffer: {buffer}");
}
Output
sent: ["batch 1", "batch 2"]
current buffer: batch 3

16. Assigning a new value drops the old one

When you assign to a variable that still owns a value, the old value is dropped right there, before the new one is stored.

Rust
fn main() {
    let mut status = String::from("starting");
    println!("{status}");
    status = String::from("running"); // "starting" is freed here
    println!("{status}");
    status = String::from("stopped"); // "running" is freed here
    println!("{status}");
} // "stopped" is freed here
Output
starting
running
stopped

17. Dropping a value early with drop

drop(value) frees a value immediately, instead of waiting for the end of the scope. It works by taking ownership of the value, so the variable cannot be used afterwards.

Rust✗ does not compile · E0382
fn main() {
    let session_token = String::from("a1b2c3");
    drop(session_token);
    println!("{session_token}");
}
Output
error[E0382]: borrow of moved value: `session_token`
 --> src/main.rs:4:16
  |
2 |     let session_token = String::from("a1b2c3");
  |         ------------- move occurs because `session_token` has type `String`, which does not implement the `Copy` trait
3 |     drop(session_token);
  |          ------------- value moved here
4 |     println!("{session_token}");
  |                ^^^^^^^^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     drop(session_token.clone());
  |                       ++++++++

Dropping early is useful for large buffers you no longer need, and later for releasing locks as soon as possible (see Shared State).

18. Values are dropped in reverse order of creation

Values in one scope are dropped last-created-first. You can't see this with String, because it prints nothing when freed; Drop, Deref & Cow shows it with a type that does. The order matters because later values may depend on earlier ones.

Rust
fn main() {
    let config = String::from("config");
    let connection = format!("connection using {config}");
    let request = format!("request over {connection}");
    println!("{request}");
} // dropped in this order: request, connection, config
Output
request over connection using config

19. A block can hand ownership out

A block's value is moved to whoever receives it. The value outlives the block because it now has a new owner.

Rust
fn main() {
    let banner = {
        let mut text = String::from("*** ");
        text.push_str("SALE");
        text.push_str(" ***");
        text // moved out of the block into `banner`
    };
    println!("{banner}");
}
Output
*** SALE ***

20. Tuples move their parts

A tuple containing a String is not a simple stack value, so it moves as a whole.

Rust✗ does not compile · E0382
fn main() {
    let user = (42, String::from("grace@example.com"));
    let copy = user;
    println!("{}", user.1);
}
Output
error[E0382]: borrow of moved value: `user`
 --> src/main.rs:4:20
  |
2 |     let user = (42, String::from("grace@example.com"));
  |         ---- move occurs because `user` has type `(i32, String)`, which does not implement the `Copy` trait
3 |     let copy = user;
  |                ---- value moved here
4 |     println!("{}", user.1);
  |                    ^^^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let copy = user.clone();
  |                    ++++++++

21. Partial moves out of a tuple

You can move one field out of a tuple and keep using the others. The tuple as a whole can no longer be used, because it is missing a part.

Rust
fn main() {
    let user = (42, String::from("grace"), String::from("grace@example.com"));
    let email = user.2;            // moves only the email out
    println!("id still usable: {}", user.0);
    println!("name still usable: {}", user.1);
    println!("moved email: {email}");
}
Output
id still usable: 42
name still usable: grace
moved email: grace@example.com

Using the whole tuple after the partial move is an error:

Rust✗ does not compile · E0382
fn main() {
    let user = (42, String::from("grace"), String::from("grace@example.com"));
    let email = user.2;
    let everything = user;
    println!("{email}");
}
Output
error[E0382]: use of partially moved value: `user`
 --> src/main.rs:4:22
  |
3 |     let email = user.2;
  |                 ------ value partially moved here
4 |     let everything = user;
  |                      ^^^^ value used here after partial move
  |
  = note: partial move occurs because `user.2` has type `String`, which does not implement the `Copy` trait

22. Destructuring moves each part to a new owner

Rust
fn main() {
    let order = (String::from("ORD-7"), vec![String::from("lamp"), String::from("bulb")]);
    let (id, items) = order;
    println!("{id} has {} items: {:?}", items.len(), items);
}
Output
ORD-7 has 2 items: ["lamp", "bulb"]

23. Integers inside a tuple are copied out

Moving applies only to parts that own heap data. Reading an integer field copies it and leaves the tuple complete.

Rust
fn main() {
    let product = (String::from("mug"), 12, 4.5);
    let quantity = product.1;
    let price = product.2;
    println!("{} x {} at {}", quantity, product.0, price);
    println!("{:?}", product);
}
Output
12 x mug at 4.5
("mug", 12, 4.5)

24. Realistic example: moving messages between queues

A worker takes messages from an inbox, processes them, and moves them to either the "done" list or the "dead letter" list. Every String has exactly one owner at every moment, and nothing is copied.

Rust
fn main() {
    let mut inbox = vec![
        String::from("charge card #1001"),
        String::from("send receipt #1001"),
        String::from("charge card #1002 INVALID"),
        String::from("send receipt #1002"),
    ];
    let mut done = Vec::new();
    let mut dead_letters = Vec::new();

    while !inbox.is_empty() {
        let message = inbox.remove(0); // ownership moves from inbox to `message`
        if message.contains("INVALID") {
            dead_letters.push(message); // ...then to dead_letters
        } else {
            println!("processed: {message}");
            done.push(message); // ...or to done
        }
    }

    println!("done: {}", done.len());
    println!("dead letters: {:?}", dead_letters);
    println!("inbox empty: {}", inbox.is_empty());
}
Output
processed: charge card #1001
processed: send receipt #1001
processed: send receipt #1002
done: 3
dead letters: ["charge card #1002 INVALID"]
inbox empty: true

25. Realistic example: partitioning log lines by level

Consuming a vector with for line in lines and pushing each line into one of two new vectors moves every line exactly once. The original vector is used up, which is fine because we no longer need it.

Rust
fn main() {
    let lines = vec![
        String::from("INFO server started"),
        String::from("ERROR disk full"),
        String::from("INFO request ok"),
        String::from("ERROR timeout contacting db"),
    ];

    let mut errors = Vec::new();
    let mut others = Vec::new();
    for line in lines {
        if line.starts_with("ERROR") {
            errors.push(line);
        } else {
            others.push(line);
        }
    }

    println!("{} errors:", errors.len());
    for e in &errors {
        println!("  {e}");
    }
    println!("{} other lines", others.len());
}
Output
2 errors:
  ERROR disk full
  ERROR timeout contacting db
2 other lines

26. Realistic example: building a document and handing it off

A function builds a String and returns it. The caller becomes the owner, then moves it into an archive. At the end, the archive owns every document, and dropping it frees them all.

Rust
fn render_invoice(number: u32, customer: &str, amount_cents: u64) -> String {
    let mut doc = String::new();
    doc.push_str(&format!("INVOICE #{number}\n"));
    doc.push_str(&format!("Bill to: {customer}\n"));
    doc.push_str(&format!("Amount due: {}.{:02}", amount_cents / 100, amount_cents % 100));
    doc // ownership moves to the caller
}

fn main() {
    let mut archive: Vec<String> = Vec::new();

    let invoice = render_invoice(1001, "Acme Corp", 125_050);
    println!("{invoice}\n");
    archive.push(invoice); // ownership moves into the archive

    archive.push(render_invoice(1002, "Globex", 9_999));

    println!("archived {} invoices", archive.len());
    println!("second is #1002: {}", archive[1].starts_with("INVOICE #1002"));
} // `archive` is dropped, freeing both invoices
Output
INVOICE #1001
Bill to: Acme Corp
Amount due: 1250.50

archived 2 invoices
second is #1002: true

push_str(&format!(...)) needs the & because push_str reads text without taking ownership of it. That is a borrow, and it is where this section is heading. The next lesson covers the other half of the story first: copying and cloning.

Summary

  • The owner frees the value at the end of its scope, at a known point.
  • b = a moves ownership; a can no longer be used (unless the type is Copy).
  • v.push(x) moves x into the vector.
  • Every value has exactly one owner at any moment.
  • Cleanup happens automatically when the owner goes out of scope.
  • Moves copy only the small stack header. Heap data is never copied implicitly.
  • Using a value after it moved is a compile error (E0382), even if the move happened in only one branch or a previous loop iteration.
  • You cannot move out of a Vec by index (E0507); use remove, pop, or a consuming for loop.
  • drop(x) frees a value early. Assigning a new value frees the old one.

Next: Move, Copy & Clone.