Vibe Engines
YouTube

Lesson 4.2 · 10 min

Handing over the key

Pass a message to a function and Chatterbox cannot use it afterwards. That is not a bug: the function took the key. This lesson is about reading the compiler error that tells you so.

Adds to ChatterboxSpot why the old message disappears.

Giving the key away

In lesson 4.1 every value had one key. What if a function needs the message? There are two honest answers: give it the key, or lend it. Lending is the next lesson. This one is giving, which Rust calls a move.

Chatterbox gets an archive function that wants to keep a message for itself. It takes a String, which is the whole value and its key. Read the program, then guess what the compiler thinks of it.

PredictArchive a message, then say it again
fn archive(msg: String) {
println!("[archived] {msg}");
}
 
fn main() {
let msg = String::from("/help");
archive(msg);
println!("you said: {msg}");
}

Does this compile — and if it does, does it run?

  • It compiles and runs
  • Compile error E0382
  • It compiles, then panics
Reveal the answer

It does not compile: E0382. Calling archive(msg) hands the key to the function. When archive ends, the message is cleaned up. The next line asks for a message that no longer exists, so the compiler refuses.

error[E0382]: borrow of moved value: `msg`
 --> src/main.rs:8:26
  |
6 |     let msg = String::from("/help");
  |         --- move occurs because `msg` has type `String`, which does not implement the `Copy` trait
7 |     archive(msg);
  |             --- value moved here
8 |     println!("you said: {msg}");
  |                          ^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `archive` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:17
  |
1 | fn archive(msg: String) {
  |    -------      ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     archive(msg.clone());
  |                ++++++++

Watch the key change hands

Step through it. At the call, the arrow does not stretch to the function and leave a copy behind. It moves: the old name keeps its place on the page but holds nothing.

Ownership Stagearchive(msg) moves the String into the function

Step through the program. Solid arrows are ownership; dashed arrows are borrows. Use ← → or the slider.

fn archive(msg: String) {
println!("[archived] {msg}");
}
 
fn main() {
let msg = String::from("/help");
archive(msg);
println!("you said: {msg}");
}
  1. Line 5. The program starts in main.
  2. Line 6. The message is made. msg owns it: msg holds the one key.
  3. Line 7. archive(msg) hands the key over. The parameter msg inside archive is now the owner, and main's msg is marked moved. Nothing was copied; only the key travelled.
  4. Line 2. archive prints the message it now owns. stdout: [archived] /help
  5. Line 3. archive ends. Its msg was the owner and goes out of scope, so the text is cleaned up here.
  6. Line 8. main asks for msg again, but its key was given away on line 7 and the text no longer exists. The compiler stops the build with E0382 before the program ever runs.

Reading the error

Now ask the compiler directly. Below is what it prints, and a plain-words reading of each part. The error looks long, but it is telling a three-line story: where the value was made, where the key was handed away, and where you tried to use it anyway.

Compiler CoachThe compiler, translated

This is the exact output of the real compiler. Toggle the English, then pick a fix and see what actually happens.

fn archive(msg: String) {
println!("[archived] {msg}");
}
 
fn main() {
let msg = String::from("/help");
archive(msg);
println!("you said: {msg}");
}
error[E0382]: borrow of moved value: `msg`
 --> src/main.rs:8:26
  |
6 |     let msg = String::from("/help");
  |         --- move occurs because `msg` has type `String`, which does not implement the `Copy` trait
7 |     archive(msg);
  |             --- value moved here
8 |     println!("you said: {msg}");
  |                          ^^^ value borrowed here after move
  |
note: consider changing this parameter type in function `archive` to borrow instead if owning the value isn't necessary
 --> src/main.rs:1:17
  |
1 | fn archive(msg: String) {
  |    -------      ^^^^^^ this parameter takes ownership of the value
  |    |
  |    in this function
help: consider cloning the value if the performance cost is acceptable
  |
7 |     archive(msg.clone());
  |                ++++++++

E0382 in the compiler error atlas →

  1. borrow of moved value: `msg`: you used msg on line 8, but its key is gone.
  2. move occurs because `msg` has type `String`, which does not implement the `Copy` trait (a trait is a named ability; you meet traits properly in chapter 8): a String is not cheap to duplicate, so passing it hands over the key instead of making a second one.
  3. The two marked places are the whole story: value moved here on line 7 and value borrowed here after move on line 8.
  4. help: consider changing this parameter type in function `archive` to borrow instead: the compiler is already pointing at the other answer, which you meet next lesson.

Fixes that were compiled for real

  • Use it first, hand it over last — compiles. The key moves on the last line, after every use. Free, and often the simplest fix.
  • Lend it instead: take &str — compiles. archive only reads the message, so it never needed the key. The & means "look, do not take". The next lesson is all about this.
  • Make a second copy with .clone() — compiles, with a cost. It compiles because there are now two values with two keys. It also copies all the text. Fine for a small message, wasteful in a loop. Treat it as a choice, not a reflex.
  • Trap: copy the name first — still E0382. Writing <code>let keep = msg;</code> does not duplicate anything. It moves the key to <code>keep</code>, so msg is gone again. A new name is not a new key.

Why numbers do not move

A whole number is a few bytes that describe themselves completely, so duplicating one is free and has no heap block to clean up. Rust marks such types Copy: handing one over leaves you holding an identical one. i32, bool, single characters (a char is one letter in single quotes) and floats are Copy. String is not, because its text lives in the heap and a silent duplicate would be a hidden cost.

PredictThe same shape with a number
fn spend(tokens: i32) {
println!("spent {tokens}");
}
 
fn main() {
let tokens = 50;
spend(tokens);
println!("left {tokens}");
}

Does this compile — and if it does, does it run?

  • It compiles and runs
  • Compile error E0382
  • It compiles, then panics
Reveal the answer

It compiles and runs. An i32 is Copy. spend received its own copy and the original is untouched, so the next line is fine.

Chatterbox v0.4

Here is the program with the fix we will keep: use the message, then archive it last. The line marked in green is new. Your chapter 3 loop supplies msg from what you typed; it is written in here so the program runs on its own.

src/main.rs: Chatterbox with an archive
fn reply(msg: &str) -> String {
match msg {
"/help" => String::from("commands: /help, /quit"),
"/quit" => String::from("bye"),
_ => format!("echo: {msg}"),
}
}
 
fn archive(msg: String) {
println!("[archived] {msg}");
}
 
fn main() {
let msg = String::from("/help");
println!("{}", reply("/help"));
println!("you said: {msg}");
archive(msg);
}
$ cargo run
commands: /help, /quit
you said: /help
[archived] /help
Try itYour turn: take the key and give back a result

Make shout take the whole String, add ! to it and return it. The caller keeps working with what comes back.

Make shout take the whole String, add ! to it and return it. The caller keeps working with what comes back.

fn shout(mut msg: String) -> String {
// add a ! to the end and hand the String back
msg
}
 
fn main() {
let msg = String::from("hi");
let loud = shout(msg);
println!("{loud}");
}

Hidden tests (3): adds_a_bang stacks_up works_on_empty

This exercise runs real Rust in your browser through the official Rust Playground, so it needs JavaScript.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
What happens to a String when you pass it to a function by value?Reveal
The key moves to the function. The old name can no longer be used.
Name two ways to fix error E0382 that cost nothing.Reveal
Use the value before handing it over, or lend it with a reference so the function never takes the key.
Why can you use an i32 after passing it to a function?Reveal
It is Copy: the function got its own duplicate, so the original key still exists.

That is lesson 4.2. Tick it off so the course remembers where you are.

Next: 4.3 Borrowing →
Learn Rust by building a chat app · Lesson 4.2 · every compiler message above is real output from rustc 1.99.0 (b940084d7 2026-09-28) (edition 2024) · Vibe Engines · 2026