Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

Learn RustChapter 4: Memory

Lesson 4.7 · 9 min

Chapter review: Memory

Chapter 4 was the one Rust is famous for: where values live, who holds the one key, and the two ways to lend it. Here it all is in one sitting: a mixed quiz where every answer came from the real compiler, and one exercise that pulls a command apart, keeps a transcript and hands back answers it owns.

Adds to ChatterboxNothing new: a mixed quiz on the whole chapter, and a bigger exercise that uses all of it.

What chapter 4 gave Chatterbox

Chatterbox 0.4 remembers your last message for /again, keeps a transcript for /history, answers /say with whatever follows it, and tells you when a command is unknown. Under those four features sit the ideas of the chapter:

  • Lesson 4.1, where values live: a value whose size is known while compiling sits in a frame on the stack; text that can grow lives in a heap block, and a String's 24 bytes in the frame are a pointer, a length and a capacity.
  • Lesson 4.2 and lesson 4.3, one owner, and handing over the key: every value has exactly one owner and is cleaned up when that owner goes away. Passing a String by value or assigning it moves the key, and the old name is done (E0382). Small Copy values such as numbers are duplicated instead, and .clone() makes a real second room when you ask for it.
  • Lesson 4.4, borrowing: & lends a value to look at; the owner keeps the key. While a loan is still going to be used, the owner may not change the value (E0502).
  • Lesson 4.5, borrowing to change: &mut lends with edit rights, to exactly one editor at a time (E0499), and only from a value made with mut.
  • Lesson 4.6, slices: &line[a..b] is a borrowed window onto part of the text. It copies nothing, counts bytes, and keeps the loan alive like any other borrow.

Nothing new below. The questions jump between those ideas on purpose, because the first job on each one is working out which rule it is about. Commit to an answer before you open it.

Mixed quiz

Eight short programs. Some ask whether the compiler accepts the program, and if not, which error it gives; some ask what it prints. Every answer was produced by running the real Rust compiler on exactly this code.

PredictKeeping the same message twice
fn main() {
let said = String::from("hello");
let mut last = String::new();
for _ in [1, 2] {
last = said;
}
println!("{last}");
}

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

  • It compiles and runs
  • Compile error E0382
  • Compile error E0499
Reveal the answer

It does not compile: E0382. The first turn of the loop moves the key from said to last. The second turn tries to move it again, but said owns nothing any more, so the compiler reports E0382 and says the value was moved "in previous iteration of loop". A plain = moves a String exactly like passing it to a function. Revisit lesson 4.3.

error[E0382]: use of moved value: `said`
 --> src/main.rs:5:16
  |
2 |     let said = String::from("hello");
  |         ---- move occurs because `said` has type `String`, which does not implement the `Copy` trait
3 |     let mut last = String::new();
4 |     for _ in [1, 2] {
  |     --------------- inside of this loop
5 |         last = said;
  |                ^^^^ value moved here, in previous iteration of loop
  |
help: consider cloning the value if the performance cost is acceptable
  |
5 |         last = said.clone();
  |                    ++++++++
PredictHow long, and how big?
use std::mem::size_of;
 
fn main() {
let banner = String::from("Chatterbox v0.4");
let name = &banner[..10];
println!("{} {}", name.len(), size_of::<&str>());
}

What does this print?

  • 10 16
  • 10 24
  • 15 16
  • 16 10
Reveal the answer

The program prints: 10 16 The slice covers the first 10 bytes, Chatterbox, so its len() is 10. Every &str is 16 bytes in the frame, a pointer and a length, however much text it looks at; 24 would be a String, which also carries a capacity. Revisit lesson 4.1 for the sizes and lesson 4.6 for the slice.

PredictAdding to the history while a slice looks at it
fn main() {
let mut history = String::from("you: hi\n");
let first = &history[..7];
history.push_str("you: again\n");
println!("{first}");
}

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

  • It compiles and runs
  • Compile error E0502
  • Compile error E0382
Reveal the answer

It does not compile: E0502. A slice is a borrow, and first is used on the last line, so its loan is still out when push_str wants to change history. Growing the String might move its text to a bigger block and leave the slice pointing at the old one, so the compiler reports E0502. Print first before the push and it compiles. Revisit lesson 4.4 and lesson 4.6.

error[E0502]: cannot borrow `history` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let first = &history[..7];
  |                  ------- immutable borrow occurs here
4 |     history.push_str("you: again\n");
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{first}");
  |                ----- immutable borrow later used here
PredictPassing the counter along
fn bump(mut count: u32) -> u32 {
count += 1;
count
}
 
fn main() {
let messages_seen = 4;
let next = bump(messages_seen);
println!("{messages_seen} {next}");
}

What does this print?

  • 4 5
  • 5 5
  • 4 4
Reveal the answer

The program prints: 4 5 A u32 is Copy: its 4 bytes are the whole value, so bump receives its own duplicate. Changing that duplicate leaves messages_seen at 4, and the returned 5 lands in next. Nothing moved, so both names still work. Revisit lesson 4.3, "Why numbers do not move".

PredictA transcript that was not allowed to change
fn main() {
let history = String::new();
history.push_str("you: hi\n");
print!("{history}");
}

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

  • It compiles and runs
  • Compile error E0596
  • Compile error E0384
Reveal the answer

It does not compile: E0596. push_str needs an editing loan of the String, and an editing loan can only be made from a value its owner allows to change. history was made without mut, so the compiler reports E0596, cannot borrow as mutable. E0384 would be assigning a new value with =; this is a change through a method. Revisit lesson 4.5.

error[E0596]: cannot borrow `history` as mutable, as it is not declared as mutable
 --> src/main.rs:3:5
  |
3 |     history.push_str("you: hi\n");
  |     ^^^^^^^ cannot borrow as mutable
  |
help: consider changing this to be mutable
  |
2 |     let mut history = String::new();
  |         +++
PredictTwo rooms, two keys
fn main() {
let said = String::from("hi");
let mut last = said.clone();
last.push_str("!");
println!("{said} {last}");
}

What does this print?

  • hi hi!
  • hi! hi!
  • hi hi
Reveal the answer

The program prints: hi hi! .clone() makes a second String with its own heap block and its own key, so last and said share nothing. Adding to last changes only its block. That independence is exactly what a clone costs you a copy for. Revisit lesson 4.3.

PredictClearing the line after the slice is finished
fn main() {
let mut line = String::from("/say hi");
let cmd = &line[..4];
println!("{cmd}");
line.clear();
println!("[{line}]");
}

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

  • It compiles and runs
  • Compile error E0502
  • Compile error E0499
Reveal the answer

It compiles and runs. A borrow lasts from where it is made to its last use, not to the end of the block. cmd is last used on line 4, so by the time clear changes line the loan is over, and the program prints /say and then []. Compare it with the history question above: same shape, different order. Revisit lesson 4.4.

PredictOne transcript, two editing loans
fn copy_into(from: &mut String, to: &mut String) {
to.push_str(from);
from.clear();
}
 
fn main() {
let mut transcript = String::from("you: hi\n");
copy_into(&mut transcript, &mut transcript);
print!("{transcript}");
}

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

  • It compiles and runs
  • Compile error E0499
  • Compile error E0502
Reveal the answer

It does not compile: E0499. Both arguments are editing loans of the same transcript, and both are alive for the whole call. Two &mut at once is E0499: inside the function, clearing one would empty the other while it was being copied. E0502 is for an editor meeting a reader. Revisit lesson 4.5.

error[E0499]: cannot borrow `transcript` as mutable more than once at a time
 --> src/main.rs:8:32
  |
8 |     copy_into(&mut transcript, &mut transcript);
  |     --------- ---------------  ^^^^^^^^^^^^^^^ second mutable borrow occurs here
  |     |         |
  |     |         first mutable borrow occurs here
  |     first borrow later used by call

The milestone: answer a line, keep the transcript

Now use the whole chapter at once. Three functions, framed exactly as Chatterbox needs them:

  • split_command cuts a line at its first space and gives back both parts as a pair, a tuple from lesson 2.6. Both parts are slices of the line: nothing is copied. With no space, the second part is "".
  • record borrows the transcript with edit rights and adds one line, such as you: hello and a newline.
  • answer borrows the line, borrows the transcript with edit rights, and gives back a String the caller owns. /say hi there answers hi there, and a bare /say answers say what?. /history answers a copy of the whole transcript, or nothing said yet. Any other word starting with / answers unknown command: and that word. Anything else is a message: record it as you, record the reply as bot, and answer you said: and the line. Commands are never recorded.

Two places need the chapter's judgement rather than its syntax. /history must hand the caller a String of its own while the transcript stays where it is, so that is the one honest place for a clone. And for a message, the reply is used by record and then handed to the caller: use it first, hand it over last.

Try itMilestone: answer a line, keep the transcript

Write split_command, record and answer. The program in main plays four lines; the hidden tests also try a bare /say, two spaces in a row, an empty history, a slash in the middle of a word, and a line that is a borrowed String.

Write split_command, record and answer. The program in main plays four lines; the hidden tests also try a bare /say, two spaces in a row, an empty history, a slash in the middle of a word, and a line that is a borrowed String.

/// Cuts a line at its first space and gives back both parts as a pair:
/// (the part before the space, the part after it). The space is in neither.
/// With no space, the whole line is the first part and the second is "".
fn split_command(line: &str) -> (&str, &str) {
todo!()
}
 
/// Adds one line to the end of the transcript: who, then ": ", then text, then a newline.
fn record(transcript: &mut String, who: &str, text: &str) {
todo!()
}
 
/// Answers one line and hands the caller an answer it owns.
/// "/say" and its text: the text, or "say what?" when there is none
/// "/history": a copy of the transcript, or "nothing said yet" when it is empty
/// any other "/word": "unknown command: " and the word
/// anything else: a message: recorded as "you" then as "bot", answered "you said: " and the line
/// Commands are never recorded.
fn answer(line: &str, transcript: &mut String) -> String {
todo!()
}
 
fn main() {
let mut transcript = String::new();
for line in ["hello", "/say hi there", "/history", "/dance now"] {
println!("bot: {}", answer(line, &mut transcript));
}
}

Hidden tests (9): a_slash_inside_a_word_is_still_a_message a_message_is_answered_and_recorded_twice history_is_a_copy_and_the_transcript_stays no_space_means_an_empty_second_part record_adds_one_line_and_keeps_the_rest say_answers_its_text_and_records_nothing splits_at_the_first_space the_caller_keeps_its_line unknown_commands_are_named_by_their_first_word

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

What you have now

If the tests are green, you just wrote, without a single unnecessary copy, the three moves the chapter is about: windows onto text you do not own, a loan with edit rights that ends with each call, and a new value whose key you hand to the caller. The clone in /history is there because the caller really needs a room of its own, not to quiet the compiler.

Chatterbox now knows five commands, but its messages and commands are still loose pieces of text and loose variables in main. The next chapter gives them shapes of their own: a Message that bundles who said what, and a Command that can only be one of the commands Chatterbox knows.

Self-check

Say it out loud, then reveal

Self-checkRetrieval beats re-reading
In one sentence each: what do moving, borrowing with &, and borrowing with &mut do to the key?Reveal
A move hands the key to a new owner and the old name is done. & lends a look while the owner keeps the key. &mut lends edit rights, to one borrower at a time, and the owner still keeps the key.
Why is a slice of a String a borrow, and what does that stop you doing?Reveal
It points into the String's heap block and owns nothing. While it is still going to be used, the owner may not change the String, because the text could move or vanish under it (E0502).
Your code fails with E0382. Name a fix that costs nothing and one that costs a copy.Reveal
Free: use the value before handing it over, or lend it with & instead of passing it by value. Costs a copy: .clone(), which makes a second String with its own heap block.
Why is a String 24 bytes whatever its text, while a u32 can be copied freely?Reveal
A String's 24 bytes are directions to a heap block (pointer, length, capacity), so copying them would give two owners of one block. A u32's 4 bytes are the whole value, so a duplicate is complete and nothing has to be handed back twice.
Your Chatterbox so far: v0.4 (after lesson 4.6)
Chatterbox v0.4 after lesson 4.6: the whole program
src/main.rs
// Chatterbox: a small chat program, built one lesson at a time.
use std::io;
 
/// What the bot is called. A constant: fixed for the whole program.
const BOT: &str = "Chatterbox";
/// The version as a pair of numbers: (major, minor).
const VERSION: (u32, u32) = (0, 4);
/// The longest message we accept, in bytes.
const MAX_LEN: usize = 200;
/// What /help prints.
const HELP: &str = "commands: /help, /again, /history, /say <text>, /quit";
 
/// The first line Chatterbox prints.
fn banner() -> String {
format!("{} v{}.{}", BOT, VERSION.0, VERSION.1)
}
 
/// The greeting, with a name filled in.
fn greeting(name: &str) -> String {
let mut text = format!("Hi {}! I'm {}.", name, BOT);
text.push_str(" Ask me anything.");
text
}
 
/// True when the text is a command: it starts with a slash.
/// It only looks at the text, so it borrows it (&str) and never owns it.
fn is_command(text: &str) -> bool {
text.starts_with('/')
}
 
/// The first word of the text: a slice (&str) pointing into it, not a copy.
fn first_word(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[..i],
None => text,
}
}
 
/// Everything after the first space, or "" when there is no space.
fn rest_of(text: &str) -> &str {
match text.find(' ') {
Some(i) => &text[i + 1..],
None => "",
}
}
 
/// Add one line to the transcript. `log` is borrowed mutably (&mut):
/// this function may change it, and nobody else can touch it meanwhile.
fn record(log: &mut String, who: &str, text: &str) {
log.push_str(who);
log.push_str(": ");
log.push_str(text);
log.push('\n');
}
 
/// What the bot says back to one message.
fn reply(message: &str) -> String {
if message.len() > MAX_LEN {
format!("that is too long (the limit is {} bytes)", MAX_LEN)
} else if message.ends_with('?') {
String::from("Good question! I can only echo for now. Ask me again in chapter 9.")
} else {
format!("you said: {}", message)
}
}
 
fn main() {
let mut messages_seen: u32 = 0;
let quiet = false; // true skips the greeting
let mut last = String::new(); // the last thing you said, for /again
let mut transcript = String::new(); // everything said so far
 
println!("{}", banner());
println!("What should I call you?");
let mut name_line = String::new();
io::stdin().read_line(&mut name_line).unwrap();
let name = name_line.trim();
if !quiet {
println!("{}", greeting(name));
}
 
let mut line = String::new();
loop {
line.clear();
let bytes = io::stdin().read_line(&mut line).unwrap();
if bytes == 0 {
break; // no more input: the keyboard was closed
}
let message = line.trim();
match first_word(message) {
"" => {} // nothing typed: nothing to answer
"/help" => println!("bot: {}", HELP),
"/quit" => {
println!("bot: bye, {}!", name);
break;
}
"/again" => {
if last.is_empty() {
println!("bot: nothing to repeat yet");
} else {
println!("bot: {}", reply(&last));
}
}
"/history" => {
if transcript.is_empty() {
println!("bot: nothing said yet");
} else {
print!("{}", transcript);
}
}
"/say" => println!("bot: {}", rest_of(message)),
_ => {
if is_command(message) {
println!(
"bot: I don't know the command {}. Try /help.",
first_word(message)
);
} else {
messages_seen += 1;
let said = String::from(message);
let answer = reply(&said);
record(&mut transcript, "you", &said);
record(&mut transcript, "bot", &answer);
println!("bot: {}", answer);
last = said;
}
}
}
}
println!("messages so far: {}", messages_seen);
}

Download this version (zip)Unzip, then cargo run inside the folder.

That is lesson 4.7. Tick it off so the course remembers where you are. That is also the end of chapter 4: a few minutes with the course flashcards now makes it stick, and the chapter’s cheat sheet has every idea on one screen.

Questions people ask

What does chapter 4 of the Rust course cover?

Memory and ownership: the stack and the heap, the rule that every value has one owner, moving a value and error E0382, borrowing with & and error E0502, borrowing to change with &mut and error E0499, and slices, which are borrowed parts of a value.

How do I tell E0382, E0502 and E0499 apart?

E0382 means you used a value after its key was handed away. E0502 means you changed a value while a read-only borrow of it was still going to be used. E0499 means two borrows with edit rights of the same value were alive at once.

Is it bad to use clone to fix ownership errors?

Not always, but it copies the whole value. First try using the value before handing it over, or lending it. Clone when the other code genuinely needs its own copy, such as handing someone a snapshot of a transcript that keeps changing.

Why does the borrow checker sometimes accept code that looks like the code it refused?

A borrow lasts until its last use, not until the end of the block. Move the last use of a borrow above the line that changes the value and the two no longer overlap, so the same lines in a different order compile.

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