---
title: "8.4 Moves: assignment transfers ownership"
description: "What let s2 = s1 really does to a String: why Rust moves ownership instead of copying, and the use-after-move error message read line by line."
url: "https://learnrust.net/chapter-8/moves/"
last_updated: "2026-06-12"
---

# 8.4 Moves: assignment transfers ownership

This is the lesson the chapter is named for. It's short on purpose; the idea inside is the single most important one in the course, and it deserves a page with no crowds.

## A thought experiment

Take the diagram from lesson [8.2](https://learnrust.net/chapter-8/the-stack-and-the-heap/): a `String` is a fixed-size handle in a stack frame (address, length, capacity) pointing at a heap block holding the text. Now ask what this innocent line should do:

```rust
let s1 = String::from("hello");
let s2 = s1;
```

For an `i32`, chapter 1 settled it: the value is copied into the new box, and you have two independent fives. Try to apply that to the `String`, and you must pick which part gets copied.

**Option one: copy the handle.** `s2` gets its own copy of the address, length, and capacity. Cheap! Also catastrophic. Both handles now point at the *same* heap block, and lesson [8.3](https://learnrust.net/chapter-8/scope-and-drop/) just taught what happens at the closing brace: each owner drops its value. `s2` is dropped, returning the block. Then `s1` is dropped, returning the same block again. That's a double free, one of lesson [8.1](https://learnrust.net/chapter-8/introduction-to-ownership/)'s three villains, manufactured by an assignment statement. (This is precisely the bug C++ programmers learn to fear and hand-patch; learncpp devotes a chunk of its chapter 22 to building a class that makes this mistake and then repairing it.)

**Option two: copy the heap block too.** Ask the allocator for a fresh block, duplicate the text, give `s2` a handle to the copy. Safe! But now `=` quietly costs whatever the text costs. Copy a ten-megabyte `String` with an innocent-looking assignment, and you've spent real time and memory without one visible signal in the code. Rust's designers found "the cheap-looking thing is secretly expensive" as distasteful as the double free.

So copying the handle is unsafe, and copying the block is dishonest. Rust picks neither.

## Rust's answer: the move

```rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{s2}");
}
```

```
hello
```

The handle is copied to `s2`, the heap block stays where it is, and then the crucial step: **`s1` is dead**. Not "shares the block," not "is a backup." The value *moved* from `s1` to `s2`, and `s2` is now its one owner. Rule 2 from lesson [8.1](https://learnrust.net/chapter-8/introduction-to-ownership/), one owner at a time, in action. There is exactly one live handle, so the closing brace drops the block exactly once, and the double free is structurally impossible.

This is why the course has been saying Rust teaches moves as "how assignment works." In C++, transferring ownership like this is an advanced technique with dedicated syntax, taught (in learncpp's case) fourteen chapters after assignment. In Rust it *is* assignment, whenever the type owns heap memory.

But a claim like "`s1` is dead" should make you suspicious. Its name is still in scope; nothing from chapter 2 stops the next line from saying `println!("{s1}")`. What happens if it does?

## The error message is the textbook

```rust
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{s1}");
}
```

```
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}");
  |                ^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++
```

Lesson [1.7](https://learnrust.net/chapter-1/reading-compiler-errors/) taught you to read these top to bottom, and this one is the most famous diagnostic in Rust. Line by line:

- **`borrow of moved value: `s1``**: the headline. A *moved value* is what `s1` became on line 3. ("Borrow" is chapter 9's word; for today, read it as "use." Using `s1` after its value moved is the crime.)
- **`move occurs because `s1` has type `String`, which does not implement the `Copy` trait`**: the compiler explaining *why* assignment moved rather than copied. Some types get copied on `=` after all, marked `Copy`; `String` is not one of them, for exactly the double-free reason above. The whole next lesson unpacks this line ("trait" itself is a chapter 16 word; the compiler is just naming the marker).
- **`value moved here`** pointing at `let s2 = s1;`: the move itself, located to the character.
- **`value borrowed here after move`** pointing at `{s1}`: the illegal use, located just as precisely.
- The **help**: the compiler offers a legal alternative, in diff form, the same way the typo-fixers of chapter 3 did. `.clone()` is lesson 8.7's subject, and yes, it's option two from the thought experiment, made explicit and visible. The compiler is reading the chapter ahead of you.

Take a second to appreciate what this error *is*. In C++, the use-after-move bug compiles, runs, and corrupts. Here it's a four-line explanation with the cause, the location, and a suggested fix. The error message is the textbook, and you'll meet this particular page many times; every Rust programmer does.

> **Moved is not out of scope**
>
> After `let s2 = s1;`, the *name* `s1` is still in scope (no `E0425` here). What's gone is its *value*. Keep last lesson's distinction sharp: scope is about names and braces; moves are about values and ownership. A variable can stand there, perfectly named, owning nothing.

## Moves happen wherever values change hands

Assignment between two `let`s is just the cleanest specimen. Two more habits to install now:

**Reassignment drops the old value.** If a `mut` variable holding a `String` is assigned a new one, the old value has no owner left, so it's dropped on the spot, not at the brace:

```rust
fn main() {
    let mut motto = String::from("move fast");
    println!("{motto}");
    motto = String::from("move carefully");
    println!("{motto}");
}
```

```
move fast
move carefully
```

The `"move fast"` block is returned at line 4, the moment its last owner stopped owning it; it doesn't linger until the brace, because by then nobody owns it.

**Shadowing is not moving.** Don't confuse this chapter with lesson [5.2](https://learnrust.net/chapter-5/shadowing/). `let s = s.trim()` and friends create a *new* variable that hides the old name; a move *ends a value*. Shadowing is about names; moving is about values. (When you shadow a `String` with something built from it, a move may also be involved, but the two concepts stay distinct; the quiz pokes at this.)

## Quiz time

**Question #1**

Compile or not? One sentence of why.

```rust
fn main() {
    let a = String::from("first");
    let b = a;
    let c = b;
    println!("{c}");
}
```

<details class="solution">
<summary>Show solution</summary>

Compiles, and prints `first`. The value moves from `a` to `b` to `c`, a relay with one baton; at every moment there's exactly one owner, and only the current owner (`c`) is used. Both braces-worth of cleanup amount to one drop, by `c`.

</details>

**Question #2**

Compile or not? One sentence of why.

```rust
fn main() {
    let a = String::from("first");
    {
        let b = a;
        println!("{b}");
    }
    println!("{a}");
}
```

<details class="solution">
<summary>Show solution</summary>

`error[E0382]: borrow of moved value: `a``. The value moves from `a` into the block's `b` and is *dropped at the block's closing brace* (`b` owns it, and `b`'s scope ends there). The final line then uses `a`, whose value moved away and no longer exists anywhere. Note that `a`'s name is still in scope; that's not the problem. The value is gone.

</details>

**Question #3**

After these two lines, describe the memory picture in lesson [8.2](https://learnrust.net/chapter-8/the-stack-and-the-heap/)'s terms: how many handles, how many heap blocks, and who gets dropped at the brace?

```rust
let recipe = String::from("stir twice");
let backup = recipe;
```

<details class="solution">
<summary>Show solution</summary>

One heap block (holding `stir twice`) and one *live* handle: `backup`'s. The boxes for `recipe`'s handle still physically sit in the frame, but the compiler has marked `recipe` as moved-from, and no code is allowed to look at it. At the closing brace, `backup` is dropped and returns the block; `recipe` drops nothing, because it owns nothing. Exactly one give-back for exactly one block.

</details>

The error message said it: `String` "does not implement the `Copy` trait." Which types do, why integers have been serenely ignoring this entire chapter, and what `=` means for them is next lesson's story.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
