Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
53: Stack vs Heap in Rust
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_stackthat takes aStringas an argument. - Inside the function, attempt to convert that
Stringinto a fixed-size array of bytes[u8; 5]. - Since the
Stringcould 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 returnNone. - In your
mainfunction, test this with one string that is exactly 5 characters and one that is not. - Observe how the
Stringis dropped (heap memory freed) while the array remains on the stack.
There are no comments for now.