From fighting the borrow checker to writing systems that power databases, operating systems, and high-frequency trading platforms.
A comprehensive, tier-based Rust curriculum designed to take developers from foundational understanding to top 1% Rustacean capable of building production systems. This is not a beginner's tutorial—this is deep systems thinking with Rust.
- Philosophy
- Repository Structure
- The Curriculum Tiers
- Mental Models & Deep Dives
- Syllabus Overview
- Capstone Projects
- Key Topics by Module
- Performance & Benchmarking
- How to Use This Repository
Rust's complexity isn't accidental—it's a feature. The borrow checker, ownership system, and type system are not barriers; they're guardrails that force you to think deeply about:
- Memory safety: Every allocation, every reference, every lifetime
- Concurrency: Race conditions become compile-time errors
- Performance: Zero-cost abstractions with no hidden allocations
- Systems thinking: How does this code run on actual hardware?
This curriculum teaches why before how, building mental models that let you reason about Rust code at the systems level.
rust-zero-to-hero-systems/
├── README.md # You are here
├── 01-syntax-and-ownership/ # Foundation: Ownership, Borrowing, Lifetimes
│ ├── notes/
│ │ ├── 01-ownership-model.md # The core ownership semantics
│ │ ├── 02-borrowing-deep-dive.md # Mutable vs. immutable borrows
│ │ ├── 03-lifetimes-explained.md # Lifetime elision & variance
│ │ └── 04-move-semantics.md # Move vs Copy semantics
│ └── exercises/
│ ├── 01-ownership-puzzle.rs
│ ├── 02-borrow-checker-challenges.rs
│ └── 03-lifetime-annotations.rs
│
├── 02-traits-and-generics/ # Abstraction: Trait Bounds, Generics, Polymorphism
│ ├── notes/
│ │ ├── 01-trait-fundamentals.md
│ │ ├── 02-generics-and-monomorphization.md
│ │ ├── 03-associated-types.md
│ │ ├── 04-higher-ranked-trait-bounds.md
│ │ └── 05-variance-and-type-safety.md
│ └── exercises/
│ ├── 01-trait-implementations.rs
│ ├── 02-generic-constraints.rs
│ └── 03-advanced-trait-patterns.rs
│
├── 03-async-ecosystem/ # Concurrency: Futures, Async/Await, Tokio
│ ├── notes/
│ │ ├── 01-future-trait.md # What is a Future?
│ │ ├── 02-pin-and-unpin.md # Why Pin exists
│ │ ├── 03-waker-mechanism.md # How async wakeup works
│ │ ├── 04-state-machines.md # Async/await desugaring
│ │ └── 05-tokio-internals.md # Tokio runtime architecture
│ └── exercises/
│ ├── 01-custom-future.rs
│ ├── 02-manual-state-machine.rs
│ └── 03-tokio-patterns.rs
│
├── 04-unsafe-and-ffi/ # Low-level: Unsafe, Raw Pointers, FFI
│ ├── notes/
│ │ ├── 01-unsafe-guarantees.md # What safe Rust doesn't check
│ │ ├── 02-raw-pointers.md # *const T and *mut T
│ │ ├── 03-undefined-behavior.md # UB categories & gotchas
│ │ ├── 04-ffi-basics.md # Calling C/C++ from Rust
│ │ └── 05-memory-layout.md # repr(C), padding, alignment
│ └── exercises/
│ ├── 01-unsafe-operations.rs
│ ├── 02-raw-pointer-manipulation.rs
│ └── 03-ffi-bindings.rs
│
├── 05-meta-programming/ # Macros: Declarative & Procedural
│ ├── notes/
│ │ ├── 01-macro-rules-fundamentals.md
│ │ ├── 02-declarative-macro-patterns.md
│ │ ├── 03-procedural-macros-intro.md
│ │ ├── 04-syn-and-quote.md
│ │ └── 05-derive-macros.md
│ └── exercises/
│ ├── 01-declarative-macros.rs
│ ├── 02-procedural-macro-crate/
│ └── 03-custom-derive.rs
│
├── 06-performance/ # Systems: Zero-cost, SIMD, Memory, Profiling
│ ├── notes/
│ │ ├── 01-zero-cost-abstractions.md
│ │ ├── 02-memory-layout-optimization.md
│ │ ├── 03-simd-and-vectorization.md
│ │ ├── 04-profiling-tools.md
│ │ └── 05-benchmarking-with-criterion.md
│ ├── exercises/
│ │ └── 01-memory-layout-analysis.rs
│ └── benchmarks/
│ ├── vector-iteration.rs
│ └── allocation-patterns.rs
│
├── 07-capstone-projects/ # Capstone: Build Real Systems
│ ├── project-1-grep/
│ │ ├── README.md # Grep clone specifications
│ │ ├── src/
│ │ └── tests/
│ ├── project-2-http-server/
│ │ ├── README.md # HTTP server specifications
│ │ ├── src/
│ │ └── tests/
│ └── project-3-database/
│ ├── README.md # In-memory database specs
│ ├── src/
│ └── tests/
│
├── examples/ # Intermediate Examples
│ ├── 01-interior-mutability.rs # RefCell, Rc, Mutex, Arc patterns
│ ├── 02-drop-implementation.rs
│ ├── 03-custom-iterators.rs
│ └── 04-error-handling-deep-dive.rs
│
└── docs/ # Additional Resources
├── mental-models.md
├── glossary.md
└── resources.md
Goal: Build unshakeable understanding of Rust's core value proposition.
Duration: 2-3 weeks (40-50 hours)
Key Outcomes:
- Deep understanding of ownership semantics (Move vs Copy)
- Mastery of borrowing rules (mutable & immutable)
- Lifetime annotations and elision rules
- Stack vs heap allocation patterns
Why This Matters: The borrow checker isn't a limitation—it's a proof system. Every compile error is the Rust compiler telling you about a real bug. Once you understand why the rules exist, you stop fighting them.
Mental Model:
- Every value has exactly one owner
- References are temporary loans with compile-time guarantees
- Lifetimes encode relationships between references
- The compiler's job is proving your code is memory-safe
Goal: Master Rust's type system for building composable, generic code.
Duration: 2 weeks (30-40 hours)
Key Outcomes:
- Trait-based design and composition
- Generic type parameters with constraints
- Associated types and where clauses
- Higher-ranked trait bounds (HRTBs)
- Understanding monomorphization
Why This Matters: Traits are how Rust achieves abstraction without runtime overhead. Generic code is compiled per concrete type (monomorphization), meaning you get polymorphism with zero runtime cost.
Mental Model:
- Traits define capability contracts
- Generics are compile-time code specialization
- The type system is your runtime optimizer
- Compile-time errors prevent runtime surprises
Goal: Understand async Rust deeply—not just await, but the machinery underneath.
Duration: 3 weeks (40-50 hours)
Key Outcomes:
- What a
Futureis and how it works PinandUnpinand why they matterWakermechanism for async wakeup- How
async/awaitdesugars to state machines - Tokio runtime architecture
- Async patterns and pitfalls
Why This Matters:
Async Rust seems magical until you understand Futures are just enums. Pin exists to prevent memory unsafety in self-referential structs. Once you can read desugared async code, you understand concurrency at a systems level.
Mental Model:
- Futures are lazy state machines
- Async/await is syntactic sugar for state transitions
- Waker allows the runtime to efficiently schedule work
- Zero-copy concurrency with no garbage collector
Goal: Understand when and how to use unsafe Rust, and interact with C/C++.
Duration: 2-3 weeks (30-40 hours)
Key Outcomes:
- When unsafe is necessary and justified
- Raw pointers (*const T, *mut T)
- Undefined Behavior categories and detection
- FFI with C/C++ libraries
- Memory layout (repr(C), padding, alignment)
- Writing safe abstractions over unsafe code
Why This Matters: The best Rust code minimizes unsafe, but systems programming sometimes requires it. Understanding what's safe vs unsafe lets you write correct low-level code. FFI is how Rust integrates with existing ecosystems.
Mental Model:
- Safe code has compile-time guarantees; unsafe code is your responsibility
- Raw pointers are powerful but dangerous
- UB isn't a crash—it's "anything can happen"
- Safe abstractions should be encapsulated—users never see unsafe
Goal: Build code that writes code. Understand declarative and procedural macros.
Duration: 2 weeks (25-35 hours)
Key Outcomes:
- Declarative macros (macro_rules!) and pattern matching
- Procedural macros (derive, attribute, function-like)
- The
synandquotecrates for macro development - Common macro pitfalls and hygiene
- Using macros to implement compile-time optimizations
Why This Matters: Macros are Rust's escape hatch for things the type system can't express. They're also used to generate boilerplate, implement type-level programming, and create DSLs. Understanding macro expansion helps you debug complex generated code.
Mental Model:
- Macros operate on token streams
- Declarative macros are pattern matching on syntax
- Procedural macros are functions that transform code
- Macro hygiene prevents accidental identifier capture
Goal: Write high-performance Rust with deep understanding of hardware interactions.
Duration: 3 weeks (40-50 hours)
Key Outcomes:
- Zero-cost abstractions and why they work
- Memory layout optimization (padding, alignment, cache locality)
- SIMD vectorization and packed_simd
- Profiling tools (perf, flamegraph, cargo-flamegraph)
- Benchmarking methodology with criterion
- Identifying and eliminating allocations
Why This Matters: Performance isn't an afterthought—it's a first-class citizen in systems programming. Rust's abstractions compile to optimal machine code. Understanding memory layout and cache behavior is what separates good systems code from great code.
Mental Model:
- Every abstraction should have zero runtime cost
- Allocations are visible in your code—know where they happen
- Cache locality matters more than algorithmic complexity (sometimes)
- Profiling beats guessing
Goal: Build production-quality systems that integrate all previous learning.
Duration: 4-6 weeks (60-80 hours)
Key Outcomes:
- Complete implementations of complex systems
- Testing, error handling, and code organization
- Performance optimization in real code
- Documentation and API design
The Question: Why does Rust require explicit ownership?
The Answer: Memory safety without garbage collection. Every byte of memory is accounted for at compile-time.
Key Insights:
- Ownership is not optional; it's mandatory
- Move semantics are the default for non-Copy types
- Copy types (u32, f64, bool) are exceptions: they're bitwise-copyable and cheap
- Drop trait executes automatically when ownership ends
Deep Model:
// MOVE SEMANTICS
let s1 = String::from("hello"); // s1 owns the String
let s2 = s1; // Ownership MOVES from s1 to s2
// println!("{}", s1); // ❌ COMPILE ERROR: s1 no longer owns data
// COPY SEMANTICS
let x = 5; // x owns an i32
let y = x; // x is COPIED to y, x still valid
println!("{}", x); // ✅ WORKS: i32 implements CopyThe Question: How can multiple parts of code read the same data safely?
The Answer: Borrowing rules enforced at compile time.
Key Rules:
- Either one mutable reference or many immutable references
- References cannot outlive what they reference
- You cannot mutate through an immutable reference
Why It Matters:
- No data races (checked at compile time, not runtime)
- No iterator invalidation bugs
- No use-after-free vulnerabilities
The Question: How does the compiler know a reference is valid?
The Answer: Lifetimes explicitly encode how long references live.
Mental Model:
// This function says: the returned string slice lives as long as
// the input reference lives. If either input dies before we use the result,
// the compiler rejects it.
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
// The compiler INFERS:
// fn first_word<'a>(s: &'a str) -> &'a strThe Question: If Cat is a subtype of Animal, is Vec<Cat> a subtype of Vec<Animal>?
The Answer: It depends—on variance.
Key Concept:
- Covariance: Subtype relationships are preserved (e.g., reading)
- Contravariance: Subtype relationships are reversed (e.g., functions)
- Invariance: No subtype relationships (e.g., mutable references)
This is why you can't pass &mut Vec<Cat> to a function expecting &mut Vec<Animal>—it's not type-safe.
The Question: Sometimes you need to mutate data even though you only have an immutable reference. How?
The Answer: Interior mutability patterns (RefCell, Cell, Mutex, RwLock).
Key Pattern:
// Single-threaded: RefCell
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4); // Mutate through immutable ref!
// Thread-safe: Mutex
let data = Mutex::new(vec![1, 2, 3]);
data.lock().unwrap().push(4); // Mutate from multiple threads safelySee examples/01-interior-mutability.rs for detailed analysis.
The Question: How does async/await work without threads or garbage collection?
The Answer: The compiler transforms async code into state machines, Futures are enums, and Waker enables efficient scheduling.
Key Insight:
// This async function:
async fn fetch_data() -> String {
let response = client.get("/api").await;
parse_response(response).await
}
// Desugars to something like:
enum FetchDataFuture {
GetRequest,
Parsing,
Done(String),
}The runtime polls Futures, moving them through states. Waker tells the runtime when to poll again, avoiding busy-waiting.
| Week | Focus | Topics |
|---|---|---|
| 1 | Fundamentals | Variables, types, functions, control flow |
| 2 | Ownership | Move semantics, Copy vs Move, Drop trait |
| 2-3 | Borrowing | Immutable refs, mutable refs, aliasing rules |
| 3 | Lifetimes | Lifetime syntax, elision, lifetime bounds |
Key Exercises:
- Implement a generic Stack with explicit lifetime management
- Fix compiler errors to understand borrow checker
- Design a doubly-linked list (why is this hard?)
| Week | Focus | Topics |
|---|---|---|
| 4 | Traits | Trait definitions, impl blocks, trait objects |
| 4-5 | Generics | Type parameters, monomorphization, where clauses |
| 5 | Advanced | Associated types, HRTBs, variance |
Key Exercises:
- Implement Iterator trait for custom types
- Create a generic binary search function with trait bounds
- Build a type-safe units system using associated types
| Week | Focus | Topics |
|---|---|---|
| 6 | Futures | Future trait, executors, async/await syntax |
| 7 | Pin & Waker | Why Pin is necessary, Waker mechanism |
| 7-8 | Tokio | Tokio runtime, multi-task scheduling, Mutex for async |
Key Exercises:
- Implement a custom Future
- Write async code without Tokio
- Debug async code and understand cancellation
| Week | Focus | Topics |
|---|---|---|
| 9 | Unsafe | What safe Rust checks, unsafe blocks, contracts |
| 10 | Pointers | Raw pointers, dereferencing, pointer arithmetic |
| 10-11 | FFI & UB | C interop, UB categories, detecting UB with Miri |
Key Exercises:
- Call C functions from Rust
- Implement a thread-safe linked list with raw pointers
- Identify UB in unsafe code samples
| Week | Focus | Topics |
|---|---|---|
| 12 | Declarative | macro_rules!, repetition, metavariables |
| 12-13 | Procedural | Derive, attribute, function-like macros |
| 13 | Advanced | syn, quote, macro debugging |
Key Exercises:
- Implement a custom vec! macro
- Write a procedural derive macro
- Build a DSL using macros
| Week | Focus | Topics |
|---|---|---|
| 14 | Design | Zero-cost abstractions, benchmark methodology |
| 15 | Memory | Layout, padding, cache locality, allocator patterns |
| 15-16 | Tools | Profiling, SIMD, optimization techniques |
Key Exercises:
- Analyze memory layout of structs
- Optimize hot paths with profiling data
- Vectorize algorithms with SIMD
Build three production systems, each integrating previous modules.
Learning Goals:
- File I/O and error handling
- String processing and regex
- Command-line argument parsing
- Testing and documentation
Specification:
- Match patterns in files (literal and regex)
- Support flags:
-i(case-insensitive),-n(line numbers),-v(invert match) - Handle multiple files and stdin
- Report results in standard format
Why This Project:
- Real-world tool with clear spec
- Forces you to design good error handling
- Teaches file I/O and performance considerations
Learning Goals:
- Network programming (TcpStream, TcpListener)
- Concurrency without frameworks
- HTTP protocol understanding
- Thread pooling and work queues
Specification:
- Serve static files over HTTP/1.0
- Handle GET requests with proper response codes
- Implement a thread pool for concurrent requests
- Support basic routing
Why This Project:
- Core networking and concurrency
- Understand how frameworks abstract low-level code
- Performance matters: measure response times
Architecture Hint:
TcpListener accepts connections
↓
ThreadPool distributes work
↓
Workers process HTTP requests
↓
Send responses, close connections
Learning Goals:
- Complex data structures and algorithms
- Serialization and deserialization
- Performance optimization
- Testing at scale
- Unsafe code for custom memory management
Specification:
- B-tree or Hash-based key-value store
- Custom serialization format
- LRU cache layer
- Persistent append-only log
- Concurrent read/write with Mutex/RwLock
- Transactions with rollback
Advanced Features:
- Range queries and indexed lookups
- Compression (gzip or snappy)
- Incremental backup/restore
- Memory-mapped files
Why This Project:
- Integrates Traits, Generics, Async, Unsafe, and Performance
- Real-world systems architecture
- Teaches you how databases actually work
- Benchmark and optimize critical paths
Core Concepts:
- Variables and mutability
- Ownership model (move semantics)
- Borrowing and references
- Lifetimes and lifetime parameters
- Stack vs heap allocation
- Drop trait and RAII
Real Understanding:
- Why Rust needs explicit lifetime syntax when other languages don't
- How move semantics prevent data races
- Why you can't have multiple mutable references (it's not a limitation, it's a guarantee)
Core Concepts:
- Trait definitions and implementations
- Generic type parameters
- Trait bounds and where clauses
- Associated types
- Higher-ranked trait bounds (HRTB)
- Monomorphization and code bloat
Real Understanding:
- Traits are how you express capabilities, not inheritance
- Generics are a zero-cost abstraction (code is specialized per type)
- Associated types encode relationships between types
- The difference between
fn foo<F: Fn(i32) -> i32>andfn foo<F: for<'a> Fn(&'a str) -> bool>
Core Concepts:
- Future trait and Poll
- async/await syntax
- Pin and Unpin
- Waker and task scheduling
- Tokio runtime architecture
- Async patterns (channels, select, timeout)
Real Understanding:
- Futures are just enums that implement a poll interface
- async/await is syntactic sugar for state machines
- Pin prevents self-referential structs from moving
- Waker is how the executor knows when to poll a Future again
- Tokio's work-stealing scheduler balances load across threads
Core Concepts:
- Unsafe keyword and unsafe blocks
- Raw pointers (*const T, *mut T)
- Undefined behavior categories
- FFI with C and C++
- Memory layout (repr, padding, alignment)
- Common UB pitfalls
Real Understanding:
- Unsafe code has no compile-time guarantees; you guarantee correctness
- UB is not a segfault—it's "the compiler can assume this never happens"
- FFI requires careful type mapping and safety boundaries
- Safe abstractions hide unsafe code: users should never interact with it directly
- Miri can catch many UB bugs by interpreting code in a controlled environment
Core Concepts:
- Declarative macros (macro_rules!)
- Macro repetition and metavariables
- Procedural macros (derive, attribute, function-like)
- The syn and quote crates
- Macro hygiene and scoping
- Common patterns and pitfalls
Real Understanding:
- Macros operate on token streams, not abstract syntax trees
- Declarative macros are pattern matching + expansion
- Procedural macros are Rust functions that transform code
- Macro debugging requires understanding token expansion
- Procedural macros enable compile-time optimizations
Core Concepts:
- Zero-cost abstractions
- Memory layout optimization
- Struct padding and alignment
- SIMD and vectorization
- Profiling tools (perf, flamegraph)
- Criterion benchmarking
- Allocation patterns
Real Understanding:
- Cache locality is often more important than algorithmic complexity
- Memory layout directly impacts performance
- Inlining and optimization happen at the LLVM level
- Benchmarks must be carefully designed to avoid optimization
- Profiling reveals unexpected bottlenecks
- Establish Baseline: Measure before optimizing
- Profile First: Don't optimize blind
- Measure Atomically: One change per measurement
- Use Criterion: Statistical rigor in benchmarks
- Check Assembly: Verify your code compiles optimally
- Allocations in hot paths: Every allocation is visible
- Cloning instead of borrowing: Can kill performance
- Monomorphization bloat: Generic code can increase binary size
- False sharing: Multiple threads writing to same cache line
- Contention on Mutex: Use RwLock for mostly-reads, or lock-free structures
-
Start with Module 1 (Syntax and Ownership)
- Read the notes
- Complete exercises
- Build confidence with the borrow checker
-
Continue with Module 2 (Traits and Generics)
- Practice trait design
- Understand generic specialization
-
Move to Project 1 (Grep Clone)
- Apply modules 1-2 to a real system
- Focus on clean error handling
- Skim Module 1-2 (or self-assess)
- Deep-dive Module 3 (Async)
- Study Module 4 (Unsafe & FFI)
- Complete Project 2 (HTTP Server)
- Self-assess with project challenges
- Focus on weak areas (likely Async or Unsafe)
- Tackle Project 3 (Database Engine)
- Build your own project: apply everything in a novel system
- Read the notes first: They explain why, not just what
- Compile and run code: Reading is passive; coding is active
- Fix compiler errors manually: Don't skip to solutions
- Benchmark your code: Intuition about performance is often wrong
- Teach others: Explain concepts to peers to find gaps in understanding
- Build projects: Theory without practice is incomplete
- Full curriculum: 22 weeks (20 hours/week = ~440 hours)
- Core modules (1-6): 16 weeks (~320 hours)
- With capstone projects: 22 weeks
Adjust based on prior Rust experience.
- Miri: UB detector
- Clippy: Lints
- Cargo Flamegraph: Profiling
This is an educational repository. Found an error? Want to improve explanations? Open a pull request or issue.
This repository is provided as educational material. Code examples are MIT licensed.
Start at Module 1. Build systems thinking layer by layer. Become a top 1% Rustacean.