Performance & Memory

Course 3 · Ch 5
Performance & Memory
Examining every "zero runtime cost" claim this course has made — precisely, and honestly

This course has repeatedly claimed "zero runtime cost" — the borrow checker (Course 1), lifetimes and monomorphized generics (Course 2). This chapter examines that claim directly, plus the practical tools for measuring performance in real Rust code.

What "Zero-Cost Abstraction" Actually Means

The precise definition, adopted as a Rust design principle: "what you don't use, you don't pay for; what you do use, you couldn't hand-code any better." This does not mean "free" in an absolute sense — an abstraction still costs whatever the equivalent hand-written code would cost. It just adds no extra overhead on top of that. Every concrete instance from this course fits this precisely:

  • Ownership/borrowing (Course 1, Ch.3–4) — checked entirely at compile time, zero runtime check
  • Generics (Course 2, Ch.3) — monomorphized, byte-for-byte identical to hand-written per-type code
  • Iterators (Course 1, Ch.8) — compile down to a tight loop, no allocation overhead from the abstraction itself

Stack vs. Heap, Revisited

Course 1 Chapter 3 introduced this briefly; here's why it actually matters for performance. Stack allocation is just moving a pointer — extremely fast, no allocator involved at all. Heap allocation goes through the allocator — real bookkeeping, genuinely slower. A practical habit, not a hard rule: prefer stack-allocated data (fixed-size arrays, Copy types) where reasonable; reach for heap types (Box, Vec, String) when size is genuinely unknown at compile time or ownership needs to move around.

Profiling Tools

  • cargo build --release — always profile release builds; debug builds disable most optimizations and can be dramatically, misleadingly slower
  • criterion crate — proper statistical benchmarking, not a one-off stopwatch timer
  • perf (Linux) / native OS profilers — CPU profiling, flame graphs
  • valgrind --tool=massif or similar — heap profiling, memory usage over time

Contrast With Go's GC-Based Approach

This is the full circle back to Course 1 Chapter 1's founding question: how does Rust get memory safety without a GC? Go's garbage collector gives automatic memory management, at real cost: unpredictable pause-time risk (even with Go's modern low-latency concurrent GC, some structural risk remains), continuous background CPU for collection cycles, and typically higher baseline memory usage (a GC often keeps more memory reserved than strictly needed, to reduce collection frequency). Rust's compiler-verified model gives deterministic, GC-pause-free performance and typically lower memory overhead — at the cost of a real learning curve, and genuinely more upfront work at the type-design stage to get ownership right, rather than letting a collector handle it later.

Go (GC)Rust (Ownership)
Memory managementAutomatic, runtime GCCompile-time verified, deterministic
Pause-time riskStructurally present, even if minimizedNone — no GC exists
Baseline memoryTypically higher (GC headroom)Typically lower
Upfront costLower — write code, let GC handle itHigher — ownership design happens upfront

Neither is simply better — two legitimate, different answers to the same underlying memory-management problem, each with real, honest trade-offs.

Zero-Cost Abstraction

No extra overhead beyond what hand-written equivalent code would already cost.

Stack vs. Heap

Stack: pointer bump, near-free. Heap: real allocator overhead — a genuine performance-relevant choice.

Release Builds + criterion

Never profile debug builds; use proper statistical benchmarking, not a stopwatch.

Go's GC Trade-offs

Automatic and simpler upfront, at the cost of pause-time risk and higher baseline memory.

Every Zero-Cost Claim, Together
Ownership/borrowing (Course 1), lifetimes and monomorphized generics (Course 2), and now iterators and abstractions generally (this chapter) all obey the exact same principle. This isn't a grab-bag of unrelated performance tricks — it's one coherent design philosophy, applied consistently, that's been the throughline of this entire three-course track since Course 1 Chapter 1 first asked how Rust could deliver memory safety without a garbage collector.
Never Draw Performance Conclusions From a Debug Build
Debug builds disable most optimizations for faster compile times and better debugging info (accurate stack traces, no inlining) — a debug build can be dramatically slower than the equivalent release build, and its relative performance characteristics (which code path is actually faster) can be genuinely misleading, not just uniformly slower. Always benchmark with --release.

Coding Challenges

Challenge 1

Write a short explanation of why Vec::new() followed by 1,000 .push() calls is slower in a debug build than a release build, referencing specifically what optimizations a release build enables that a debug build doesn't.

📄 View solution
Challenge 2

Given a choice between [i32; 100] (a fixed-size stack array) and Vec with 100 elements for a function that only ever needs exactly 100 known-at-compile-time integers, explain which is the better default choice and why, referencing this chapter's stack-vs-heap performance discussion.

📄 View solution
Challenge 3

Trace the "zero-cost abstraction" thread across this entire 3-course track: name one specific example each from Course 1, Course 2, and this chapter, and explain in one sentence what each one avoids paying for at runtime.

📄 View solution

Chapter 5 Quick Reference

  • Zero-cost abstraction: no overhead beyond what hand-written equivalent code would already cost — not "free"
  • Stack allocation — a pointer bump, near-instant; heap allocation — real allocator overhead
  • Always benchmark with cargo build --release — debug builds can be dramatically, misleadingly slower
  • criterion for statistical benchmarking; perf/OS profilers for CPU; valgrind --tool=massif for heap profiling
  • Go's GC: automatic, simpler upfront, real pause-time risk and higher baseline memory
  • Rust's ownership: deterministic, zero GC runtime cost, more upfront ownership-design work
  • Ownership/borrowing, lifetimes, monomorphized generics, and iterators all obey the same zero-cost principle — one coherent philosophy, not separate tricks
  • Next chapter: Capstone — building a real CLI tool combining ownership, error handling, traits, and crates