RustArena

Step 1 of 6

Stack and heap: where values live

Every value your program works with is stored somewhere in the computer's memory. To understand Rust's most famous feature, ownership, you first need a picture of the two places a running program keeps its data: the stack and the heap.

The stack

The stack holds the local variables of the functions that are running right now. Each function call gets its own block of space, a stack frame, big enough for all of its variables. When the function returns, its whole frame is thrown away in one go. Making and removing frames is just moving a pointer up and down, so the stack is extremely fast.

The catch: everything on the stack must have a size known at compile time. An i32 is always 4 bytes, an f64 8 bytes, an array [i32; 3] 12 bytes. The compiler lays out each frame before the program ever runs.

The heap

What about text the user types in, or a list that grows while the program runs? Their size isn't known until run time, and it can change. That data goes on the heap: a big pool of memory where the program can ask for a block of any size while it runs. Asking for a block is called allocating. The allocator finds free space and hands back its address, a pointer. When the program is done with the block, it must give it back (free it) so the space can be reused.

A String uses both. The text lives on the heap. The String value itself is a small, fixed-size handle on the stack holding three numbers: a pointer to the text, its length (bytes in use) and its capacity (bytes reserved).

fn main() {
    let lives = 3;
    let name = String::from("Ferris");
}

      STACK (main's frame)                     HEAP
     ┌───────┬──────────────────┐
     │ lives │ 3                │
     ├───────┼──────────────────┤          ┌───┬───┬───┬───┬───┬───┐
     │ name  │ ptr  ────────────┼────────► │ F │ e │ r │ r │ i │ s │
     │       │ len       6      │          └───┴───┴───┴───┴───┴───┘
     │       │ capacity  6      │
     └───────┴──────────────────┘

A Vec is built the same way: pointer, length and capacity on the stack, the elements on the heap. That's why both can grow: when they run out of capacity, they allocate a bigger block on the heap and move their contents there, while the handle on the stack keeps the same size.

You can check sizes with std::mem::size_of::<T>(), which tells you how many bytes a value of type T takes on the stack. The ::<T> part names the type you're asking about. Since Rust 1.80, size_of is one of the few functions available everywhere without its full path, so you can also write just size_of::<T>(), as this example does.

fn main() {
    let short = String::from("hi");
    let long = String::from("a much longer piece of text");
    println!("i32: {} bytes", size_of::<i32>());
    println!("[i32; 3]: {} bytes", size_of::<[i32; 3]>());
    println!("String handle: {} bytes", size_of::<String>());
    println!("text on the heap: {} and {} bytes", short.len(), long.len());
}
i32: 4 bytes
[i32; 3]: 12 bytes
String handle: 24 bytes
text on the heap: 2 and 27 bytes

The handle is 24 bytes (three 8-byte numbers on a 64-bit machine) whether the text is 2 bytes or 2 gigabytes. The text itself is somewhere else.

Who cleans up the heap?

Stack memory cleans itself up when a function returns. Heap memory doesn't: something has to free each block, exactly once, after the last time it's used. Programming languages answer this in three ways:

Approach Languages The catch
You free memory by hand C, C++ (malloc/free, new/delete) Forget it: a memory leak. Free twice: a double free. Use the memory after freeing it: a use-after-free. These bugs crash programs and are the classic source of security holes.
A garbage collector finds unused memory while the program runs Java, Python, Go, JavaScript, C# Safe, but it costs CPU time and memory, and can pause the program at moments you don't control.
Ownership: the compiler works out where to free, using rules it checks at compile time Rust You have to follow the rules. That's what this module teaches.

Microsoft and Google's Chrome team have both reported that around 70% of their serious security bugs are memory-safety bugs like these. Rust's ownership rules make use-after-free and double free impossible in normal (safe) Rust, with no garbage collector and no run-time cost: the compiler inserts the code that frees memory exactly where it belongs.

Common mistakes

Your task

Fill in the blanks so the program prints the size of the String handle (the type name goes inside ::<>) and the number of bytes the city's text takes on the heap. It should print:

handle: 24 bytes
city text: 4 bytes
motto text: 17 bytes

Challenge 2

A Vec is a handle too. Fill in the type on the Vec handle line (a vector of i32) and the method that counts the vector's elements, so the program prints:

array: 16 bytes
Vec handle: 24 bytes
8 readings, still a 24-byte handle

Notice the difference: the array's numbers are all on the stack (4 × 4 bytes), while the Vec's numbers are on the heap.

Challenge 3

A String can grow because its text is on the heap. Starting from the empty String in the starter code (String::new() makes a String with no text yet), write a for loop that runs 4 times. Each time, push "==" onto bar with push_str and print bar. After the loop, print its length. The output should be:

==
====
======
========
bar: 8 bytes

Next: Owners and scope