Stack vs Heap
Understanding stack and heap is key to understanding Rust's ownership model. The stack is fast, fixed-size, LIFO. The heap is dynamic, slower, requires management.
fn main() {
// Stack: fixed-size, very fast
let x: i32 = 5; // 4 bytes on stack
let arr: [u8; 8] = [0; 8]; // 8 bytes on stack
// Heap: dynamic size, owns a pointer on stack
let s = String::from("hello");
// Stack: ptr (8 bytes) + len (8) + capacity (8) = 24 bytes
// Heap: 5 bytes for "hello"
println!("len={} cap={}", s.len(), s.capacity());
// Box: explicit heap allocation
let b = Box::new(42);
println!("boxed: {}", b);
println!("deref: {}", *b);
// When b drops, both Box and heap int are freed
}Move in Memory
fn main() {
let s1 = String::from("hello");
// s1 stack: {ptr: 0xA000, len: 5, cap: 5}
// heap 0xA000: [h,e,l,l,o]
let s2 = s1;
// s2 stack: {ptr: 0xA000, len: 5, cap: 5} <- same ptr!
// s1 stack: invalidated by compiler
// heap: unchanged
// Without this, dropping both s1 and s2 would free 0xA000 twice!
// Rust prevents this at COMPILE TIME.
// Clone makes a real copy:
let s3 = s2.clone();
// s3: {ptr: 0xB000, len: 5, cap: 5}
// heap 0xB000: [h,e,l,l,o] (new allocation)
println!("{} {}", s2, s3);
}
// RAII: Drop runs automatically
struct FileHandle { name: String }
impl Drop for FileHandle {
fn drop(&mut self) {
println!("Closing: {}", self.name);
}
}
fn main2() {
let f = FileHandle { name: "data.txt".to_string() };
println!("Using file");
} // "Closing: data.txt" - printed automatically🎯 Practice
- Create a struct with a Drop impl. Verify cleanup order (reverse of creation)
- Use Box<T> to heap-allocate a large array [i64; 10000]
- Create a recursive type: enum List { Cons(i32, Box<List>), Nil }
🎉 Key Takeaways
- Stack: fast, fixed size, LIFO. Heap: flexible, dynamic, managed via ownership
- String stores (ptr, len, cap) on stack; actual bytes on heap
- Move copies stack bytes and invalidates source - prevents double-free
- Drop trait implements automatic cleanup (RAII)