Skip to Content
Course content

53: Stack vs Heap in Rust

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Why does it even matter where my data lives?

If you've come from a language like Python or JavaScript, you probably never had to think about this. The language just "handled it." But in Rust, knowing the difference between the stack and the heap is the only way to actually understand why the borrow checker is screaming at you.

Think of the stack as a very fast, very organized pile of trays. Every piece of data on the stack must have a known, fixed size at compile time. If you create an integer or a boolean, Rust knows exactly how many bytes that takes, pushes it onto the stack, and when the function ends, it just "pops" it off. It's incredibly efficient.

The heap, however, is more like a giant, messy warehouse. You don't know exactly how much space you'll need until the program is actually running. If you're reading a file from a disk or taking user input, you can't tell the compiler, "This will be exactly 14 bytes." You ask the heap for some space, and the heap gives you a pointer—a memory address—that tells you where your data is hiding.

// This lives on the stack. Fixed size.
let x = 42; 

// The 'Vec' struct (pointer, length, capacity) is on the stack, 
// but the actual numbers [1, 2, 3] are stored on the heap.
let numbers = vec![1, 2, 3]; 

Wait, so what's actually happening with a String?

This is where most people get tripped up. In Rust, there is a big difference between a &str (a string slice) and a String. I like to think of the String type as a "heap-allocated" string.

When you create a String::from("Hello"), Rust does two things. First, it puts the actual characters "Hello" into the heap. Then, it puts a small piece of metadata on the stack. That metadata contains the pointer to the heap memory, the length of the string, and its capacity.

If you only have a &str (like a hardcoded literal), you're often just pointing to a specific spot in the compiled binary of your program. No heap allocation is needed because the text is baked into the executable itself. I usually recommend using &str whenever possible because skipping the heap makes your code faster and your memory footprint smaller.

How does this relate to ownership and moving?

This is the "aha!" moment. When you "move" a variable in Rust, you aren't usually moving the actual data on the heap—that would be expensive. You're just moving the stack metadata.

Imagine you have a String that holds a 10MB document. That document is sitting on the heap. On the stack, you have a tiny pointer to that 10MB. When you pass that String into another function, Rust doesn't copy the 10MB. It just copies the pointer, length, and capacity.

The reason Rust makes you "move" ownership is to prevent a disaster called a double-free. If two different variables on the stack both thought they "owned" that same 10MB chunk on the heap, they would both try to clear that memory when they went out of scope. That would crash your program. By moving the ownership, Rust ensures that exactly one person is responsible for cleaning up the warehouse when the job is done.

fn main() {
    let s1 = String::from("A very long string"); // Heap allocation happens
    let s2 = s1; // Only the stack metadata is copied. s1 is now invalid.
    
    // println!("{}", s1); // This would fail! s1 no longer "owns" the heap pointer.
    println!("{}", s2); // This works.
} // s2 goes out of scope here, and the heap memory is finally freed.



📋 Practical Task

Exercise: The Heap-to-Stack Data Migrator

Your goal is to create a program that demonstrates the transition from heap-allocated data to stack-allocated data. You will start with a String (heap) and convert it into a fixed-size array (stack).

  • Create a function called migrate_to_stack that takes a String as an argument.
  • Inside the function, attempt to convert that String into a fixed-size array of bytes [u8; 5].
  • Since the String could be any length, you'll need to handle the logic: if the string is exactly 5 bytes, copy it into the array. If it's longer or shorter, print an error message and return None.
  • In your main function, test this with one string that is exactly 5 characters and one that is not.
  • Observe how the String is dropped (heap memory freed) while the array remains on the stack.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.