Dynamic Memory
c1-7 named dangling pointers as the exact bug class Rust's borrow checker exists to prevent. This chapter shows where those dangling pointers actually come from — and hands you the same responsibility Rust's ownership system otherwise carries for you.
Stack vs. Heap
The stack is automatic: a local variable's memory exists for exactly as long as its function is running, freed the instant that function returns — this is precisely what made returning &local_var in c1-7 a dangling pointer. The heap is manual: memory allocated there exists until something explicitly releases it, with no connection to any particular function's lifetime, and effectively unlimited size.
malloc
Allocates raw, uninitialized memory of a given byte size, returning void * — cast or assigned to a typed pointer. On failure, it returns NULL, which must always be checked; Rust, by contrast, typically aborts automatically on allocation failure rather than returning a value the caller might forget to verify.
calloc
Like malloc, but zero-initializes the memory, and takes element count and element size as two separate arguments. The zero-init distinction genuinely matters — malloc's memory is garbage until written, and reading it before initializing is its own class of bug.
realloc
Resizes a previous allocation — and may move it to an entirely new address. The returned pointer can genuinely differ from the one passed in, and the old pointer becomes invalid the instant realloc succeeds.
free
Releases heap memory back to the system. Immediately afterward, the pointer that referenced it becomes a genuine dangling pointer — this is the exact mechanism c1-7 warned about, closing the loop directly: freeing is how dangling pointers are most commonly created in real code.
Manual Memory Management vs. Rust's Ownership
This is the chapter's real subject. In C, you decide when to call free, and nothing enforces it. Forget it — a leak. Call it twice on the same pointer — a double-free, undefined behavior. Use the pointer after freeing — use-after-free, also undefined behavior. Rust's ownership model (rust1-3) tracks, at compile time, exactly when a value's owner goes out of scope, and automatically inserts the equivalent of free at precisely that point — no leaks in safe code, no double-frees, no use-after-free, guaranteed by the compiler rather than the programmer's own discipline. Every rule this chapter just described by hand is what Rust's ownership system enforces silently, every time, without exception.
| Concept | Rust | C |
|---|---|---|
| When memory is freed | automatically, when the owner goes out of scope | only when you explicitly call free() |
| Forgetting to free | not possible in safe code | a memory leak |
| Freeing twice | not possible — ownership moves, can't double-drop | a double-free — undefined behavior |
| Using memory after it's freed | compile error — the borrow checker rejects it | a use-after-free — undefined behavior |
free immediately after an allocation, before writing the code that uses the memory in between — then move the free to its real, correct location. It's much harder to forget a free you already wrote once than one you were planning to add "later."
ptr = realloc(ptr, new_size); is a real, common bug: if realloc fails and returns NULL, that NULL overwrites ptr — permanently losing the only reference to the still-valid original allocation, which now leaks with no way to free it. Always assign to a temporary variable first.
Coding Challenges
Allocate an array of 5 ints with malloc, fill it with values 1 through 5, print them, then free the memory.
📄 View solutionAllocate an array of 3 ints with calloc, print all three values before writing anything to them, then explain what they show and why, contrasted with what malloc's uninitialized memory would show instead.
📄 View solutionExplain, precisely, why Rust's ownership model makes a double-free structurally impossible in safe code, rather than merely unlikely or discouraged.
📄 View solutionChapter 2 Quick Reference
- Stack — automatic, freed on function return; heap — manual, freed only by explicit action
malloc— uninitialized memory, must check forNULLcalloc— zero-initialized memory, count and size given separatelyrealloc— may move the allocation; always assign to a temp variable first, never the original pointerfree— releases memory; the pointer becomes dangling immediately afterward- Leaks, double-frees, use-after-free — all manual failure modes in C; all structurally prevented by Rust's ownership model at compile time
- Next chapter: Memory Bugs — the tooling (valgrind) that catches exactly these mistakes