8.3Scope and drop

Last updated June 12, 2026

Lesson 2.4 ended its scope story with a promissory note: "What happens to the value itself when its scope ends is a deeper story with its own chapter; lesson 8.3 introduces the word 'dropped' properly." This is lesson 8.3. Time to pay.

The rule

Last lesson left the chapter's central question hanging: a String's heap block doesn't free itself, so who gives it back, and when? You already know Rust's answer, because it's ownership rule 3 from lesson 8.1:

When the owner goes out of scope, the value is dropped: its memory is given back.

Unpack it with the pieces you have. The owner of the String is its variable. The variable's scope is mechanical and visible, exactly as lesson 2.4 defined it: from the let to the closing brace } of its block. So the drop happens at the brace. Not "sometime after," not "when a collector gets around to it." At the brace, the program hands the heap block back to the allocator, every time, as surely as the statements above it ran.

fn main() {
    let outer = String::from("around till the end");
    {
        let inner = String::from("short but busy");
        println!("inner says: {inner}");
    } // <- inner goes out of scope: its String is dropped, heap block returned
    println!("outer says: {outer}");
} // <- outer goes out of scope: same story
inner says: short but busy
outer says: around till the end

The output is nothing new; chapter 2 programs printed the same shape. What's new is what you know about the two closing braces. The inner brace isn't just where the name inner stops being usable (lesson 2.4's point, enforced by the E0425 you've met). It's where inner's heap block is handed back, while the rest of the program continues. The outer brace does the same for outer. A block ends, and its values' memory is already free, before the next statement runs.

For the integer-likes of chapters 1 through 4, this lesson changes nothing: lesson 2.4 said "the box is just reclaimed," and that stays the whole truth. An i32 has no heap block to return; dropping it means its frame box becomes meaningless, which the popping frame was going to accomplish anyway. Dropping only becomes visible work for types that own something beyond their box, and String is still our specimen: dropping a String returns its heap block.

Cleanup is a place, not a process

Compare this against lesson 8.1's other two answers, because the difference is the selling point.

In C, the free-the-block call is a line the programmer remembers (or forgets, or writes twice) to put somewhere. In a garbage-collected language, freeing happens eventually: when the collector next runs, which is whenever it decides, which is sometimes mid-frame in your game. In Rust, cleanup is a place in the program: that brace, there, on the screen. You can point at the exact character where every value dies.

Deterministic cleanup is why Rust programs get garbage-collector safety with no garbage collector. There's no background process, because there's nothing for one to figure out: the compiler already settled every value's drop point while compiling, and emitted the give-it-back code at each one. It's also why Rust doesn't stutter: cleanup work happens at predictable points your own code chose, never at a collector's whim.

Key insight

Read a closing brace as an event, not just punctuation. From this chapter on, } means: every value still owned by this block is dropped, right here. The compiler wrote the cleanup; the brace is where it runs.

One honest admission: today's program can't show you a drop. The block returns silently, and making a value announce its own cleanup (printing "dropped!" as it goes) needs machinery from chapter 10 onward. For now the evidence is circumstantial but damning: chapter 5 had you build Strings by the dozen with no cleanup code in sight and no leaks, and next lesson the compiler starts visibly enforcing decisions that only make sense if drops really happen at braces. They do.

For advanced readers

Within one block, values drop in reverse order of declaration (last declared, first dropped), like frames popping off a stack. It rarely matters until values depend on each other, which is a later-chapters concern; it's mentioned here so the symmetry registers: scope is a stack discipline, and so is dropping.

Quiz time

Question #1

Number the lines. On which line is each String dropped?

fn main() {              // 1
    let a = String::from("a");   // 2
    {                    // 3
        let b = String::from("b");   // 4
        println!("{b}"); // 5
    }                    // 6
    let c = String::from("c");   // 7
    println!("{a} {c}"); // 8
}                        // 9
Show solution

b is dropped at line 6, the closing brace of its block. a and c are dropped at line 9, main's closing brace (c first, then a, by the reverse-order rule from the advanced box, though any answer placing both at line 9 is full credit).

Question #2

True or false, one sentence of why: "In Rust, a String's heap memory is freed at some point after its owner goes out of scope, once the runtime notices it's no longer needed."

Show solution

False, and the word doing the damage is "notices." Nothing notices anything at runtime: the compiler determined the drop point at compile time (the owner's closing brace), and the memory is given back at that exact place, immediately, not "at some point after." The quoted sentence describes a garbage collector.

Question #3

Predict the compiler's reaction:

fn main() {
    {
        let greeting = String::from("hi");
    }
    println!("{greeting}");
}

Is the problem that greeting's value was dropped, or something the course taught long before this chapter?

Show solution

error[E0425]: cannot find value greeting in this scope. This is chapter 2's rule, not chapter 8's: the name greeting only exists from its let to its block's closing brace, so the println! line can't refer to it at all. The value was indeed also dropped at that brace, but the compiler never gets that far; the name check fails first. Keeping name-scope (chapter 2) and value-drop (this lesson) separate in your head will pay off next lesson, where a value becomes unusable while its name is still perfectly in scope.

That last sentence is the setup. Next lesson is the one this chapter is named for: what let s2 = s1; actually does to s1, and the most famous error message in Rust.