Saltar a contenido

Rust Mastery

Ownership, async, unsafe, FFI, production architecture.

Printing this page

Use your browser's PrintSave as PDF. The print stylesheet hides navigation, comments, and other site chrome; pages break cleanly at section boundaries; advanced content stays included regardless of beginner-mode state.


Rust Mastery Blueprint-A 24-Week Master-Level Syllabus

Authoring lens: Senior Principal Systems Engineer / Rust Architect. Target outcome: A graduate of this curriculum should be capable of (a) submitting non-trivial PRs against rust-lang/rust, (b) owning a low-latency fintech matching engine or risk pipeline, or (c) writing a kernel module in rust-for-linux without supervision.

This is not a "learn Rust in N days" track. It assumes the reader is already a working software engineer who can read C, has shipped production code in some language, and is willing to read source code (LLVM, Tokio, glibc, the Rust reference) as a primary learning surface.


Repository Layout

File Purpose
00_PRELUDE_AND_PHILOSOPHY.md Why Rust; the affine type system; the cost model; reading list.
01_MONTH_FOUNDATIONS.md Weeks 1–4. Toolchain, memory layout, ownership, error model.
02_MONTH_TYPE_SYSTEM.md Weeks 5–8. Lifetimes, variance, traits, smart pointers, Drop checker.
03_MONTH_CONCURRENCY_ASYNC.md Weeks 9–12. Atomics, lock-free, Pin/Unpin, Tokio & Smol internals.
04_MONTH_UNSAFE_FFI_MACROS.md Weeks 13–16. unsafe, FFI, declarative & procedural macros.
05_MONTH_PRODUCTION_ARCHITECTURE.md Weeks 17–20. Hexagonal, zero-copy I/O, observability, testing.
06_MONTH_MASTERY_CAPSTONE.md Weeks 21–24. Custom data structures, no_std, rustc internals, capstone.
APPENDIX_A_PRODUCTION_HARDENING.md LTO, PGO, BOLT, cargo-geiger, supply chain auditing.
APPENDIX_B_DATA_STRUCTURES.md Build-from-scratch reference: B-Tree, lock-free hash map, MPSC, slab.
APPENDIX_C_CONTRIBUTING_TO_RUSTC.md The compiler pipeline; bootstrap; MIR; first PR playbook.
CAPSTONE_PROJECTS.md Three terminal projects, one per career track.

How Each Week Is Structured

Every weekly module follows the same five-section format so the reader can budget time:

  1. Conceptual Core-the why, with a mental model.
  2. Mechanical Detail-the how, down to layout and ABI where relevant.
  3. Lab-a hands-on exercise that cannot be completed without internalizing the concept.
  4. Idiomatic & Clippy Drill-read 2–3 lints, refactor a sample to silence them, understand why each lint exists.
  5. Production Hardening Slice-an LTO/PGO/cross-compile/audit micro-task that compounds across weeks.

Each week is sized for ~12–16 focused hours. Skip the labs at your peril; the labs are the curriculum.


Progression Strategy

The phases form a dependency DAG, not a linear track:

Foundations ──► Type System ──► Concurrency ──► Unsafe / FFI / Macros
       │              │                │                   │
       └──────────────┴────────┬───────┴───────────────────┘
                  Production Architecture
                   Mastery & Capstone

The Production Hardening slice is intentionally orthogonal-it accumulates a hardening/ workspace that, by week 24, is a publishable Cargo template.


Non-Goals

  • This curriculum does not cover web frameworks (Axum/Actix) as primary subjects. They appear only as integration surfaces in Month 5.
  • Game development, GUI, and WASM front-ends are out of scope. Pointers are given for the curious in 00_PRELUDE_AND_PHILOSOPHY.md.
  • "Rewrite it in Rust" advocacy is explicitly avoided; the reader should finish the program able to argue against using Rust when it is the wrong tool.

Capstone Tracks (pick one in Month 6)

  1. Compiler Track-land a non-trivial PR in rust-lang/rust (e.g., a clippy lint, a diagnostic improvement, or a small MIR transform).
  2. Fintech Track-implement a multi-asset limit-order-book matching engine with sub-microsecond p99 hot-path latency, fuzzed and verified under loom.
  3. Kernel Track-write a Rust character-device driver for rust-for-linux, complete with KUnit tests and a working out-of-tree build.

Details in CAPSTONE_PROJECTS.md.

Prelude-The Philosophy Behind the Syllabus

Before week 1, sit with this document for an evening. The rest of the curriculum is mechanically dense; this is the only chapter where we step back and define the shape of the discipline.


1. Rust Is an Affine Type System Bolted to a Region Calculus

Rust is most often described as "a memory-safe systems language." That description is marketing, not engineering. The accurate description:

  • Affine types: every value can be used at most once by move (std::mem::take - style semantics), unless it isCopy`.
  • Region inference: lifetimes ('a) are region variables in a constraint solver-they exist only at compile time and never appear in the binary.
  • Sub-structural extensions: traits like Send, Sync, Unpin, Sized add capability axes that the type checker propagates.

If you internalize this framing, you stop fighting the borrow checker and start reading the constraint failures it emits.

Reading: Ralf Jung et al., RustBelt: Securing the Foundations of the Rust Programming Language (POPL 2018). Skim the introduction; you do not yet need the separation-logic semantics.


2. The Cost Model You Must Adopt

A working Rust engineer reasons about every line of code along four axes simultaneously:

Axis Question to ask
Ownership Who frees this? When?
Layout Where does this live-stack, heap, .data, .rodata, TLS? Aligned to what?
Codegen Is this monomorphized? How many copies will the linker see?
Failure What does the panic path look like? Is unwinding allowed here?

Beginner courses teach axis 1 only. This curriculum forces all four into your hands by week 8.


3. The Reading List

These are referenced throughout the curriculum. You are not expected to read them cover-to-cover before starting; they are pinned tabs.

Primary - The Rust Reference-doc.rust-lang.org/reference. The normative spec. - The Rustonomicon-doc.rust-lang.org/nomicon. Unsafe semantics. - Rust for Rustaceans (Jon Gjengset)-the only book worth reading after The Book. - Programming Rust, 2e (Blandy/Orendorff/Tindall)-best treatment of the type system.

Secondary, by phase - Concurrency: Rust Atomics and Locks (Mara Bos). Read once at week 9, again at week 11. - Async: Tokio's runtime/ source tree; withoutboats's blog (without.boats). - Macros: The Little Book of Rust Macros and dtolnay/proc-macro-workshop. - Compiler: rustc-dev-guide (rustc-dev-guide.rust-lang.org). - Allocators: Phil Opperman's Writing an OS in Rust-chapters 9–11.

Adjacent canon (not Rust, but you must know it) - Drepper, What Every Programmer Should Know About Memory (2007). Re-read in week 9. - Herlihy & Shavit, The Art of Multiprocessor Programming, chapters 7, 9, 13. - Intel SDM Vol. 3A, chapter 8 (memory ordering). RISC-V and ARMv8 equivalents if those are your targets.


4. Curriculum Philosophy: "Read the Source, Ship the Lab"

Three rules govern every module:

  1. Source first, blog second. When the curriculum says "study Tokio's Notify," it means open tokio/src/sync/notify.rs and read it. Blogs go stale; commits are dated.
  2. One lab per concept, one PR per phase. By the end of each month, the reader has produced one open-source-quality artifact (crate, gist, or PR)-not a notebook of toy snippets.
  3. The compiler is the teacher. When you do not understand why something fails to compile, the first response is to enable RUSTC_LOG=trace, the second is to consult rustc --explain Exxxx, and only the third is to ask another human.

5. What Rust Is Not For

A graduate of this curriculum should be able to argue these points in a design review without sounding ideological:

  • Greenfield CRUD web apps with frequent schema churn. Your iteration speed will be dominated by compile times and serde ceremony. Go, TypeScript, or Elixir are usually better.
  • Throwaway scripts. Use Python.
  • Code where the team has no C/C++/systems intuition. Rust does not erase complexity; it surfaces it. A team that has never debugged a use-after-free will struggle to read Pin<&mut Self>.
  • GUI applications with rich tooling demands. The ecosystem is improving but is still a step behind Qt/SwiftUI/Flutter.

The signal that Rust is the right tool: you have a memory-safety, latency-tail, or single-binary-deployment constraint that ranks above developer iteration speed.


6. A Note on AI-Assisted Workflows

Modern Rust authors use LLM tooling. Two rules:

  1. Never paste async/unsafe code from a model without reading the generated MIR (cargo rustc -- --emit=mir). The failure modes are subtle and the model's training data is biased toward older, simpler patterns.
  2. Macros are the worst LLM modality. Hygiene bugs and span errors slip past human review. Write proc macros yourself; use the model only for the boilerplate around syn::parse.

You are now ready for Week 1. Open 01_MONTH_FOUNDATIONS.md.

Month 1-Foundations: Toolchain, Memory Layout, Ownership, Errors

Goal: by the end of week 4 you can (a) describe where every value in a non-trivial program lives in memory, (b) read a borrow-checker error and locate the conflicting region without trial and error, (c) explain why ? is not exception handling, and (d) ship a small CLI as a statically linked binary with reproducible builds.


Weeks

Week 1 - The Toolchain and the Compiler Pipeline

1.1 Conceptual Core

  • rustup is a toolchain multiplexer, not a compiler. Understand the channel model (stable, beta, nightly, dated nightlies), components (rust-src, rust-analyzer, miri, rustc-dev), and target triples.
  • cargo is a build orchestrator on top of rustc. It is not the compiler. Almost every "cargo problem" is a rustc invocation problem in disguise. Learn to print the actual rustc command with cargo build -vv.
  • The compilation pipeline: source → lexer → parser → AST → HIR (high-level IR) → THIR → MIR (mid-level IR, where borrow checking runs) → LLVM IR → object code → linker. You will revisit this in Month 6; this week you only need to name the stages.

1.2 Mechanical Detail

  • rustc --print=cfg, rustc --print=target-list, rustc --print=sysroot. Learn these by muscle memory.
  • The Cargo.lock discipline: committed for binaries, gitignored for libraries-and why (the dependency-resolver contract).
  • cargo tree -d for duplicate dependency detection; cargo metadata --format-version=1 | jq for programmatic inspection.
  • The role of build.rs: when it runs, what it can emit (cargo:rustc-link-lib, cargo:rerun-if-changed), and why it is a frequent supply-chain attack vector.

1.3 Lab-"Hello World, Audited"

  1. Create hello-audited. Pin a specific stable toolchain via rust-toolchain.toml.
  2. Build with - -release. Runobjdump -h target/release/hello-auditedand identify the.text,.rodata,.data,.bss, and.eh_frame` sections.
  3. Strip with strip -s and compare binary sizes. Now rebuild with RUSTFLAGS="-C strip=symbols -C panic=abort" and compare again.
  4. Document the size delta from each flag in NOTES.md. You should observe .eh_frame shrinking dramatically when panic=abort is set-explain why.

1.4 Idiomatic & Clippy Drill

  • Enable #![deny(clippy::pedantic, clippy::nursery)] in your crate root. Half of the lints will fire on idiomatic-looking code. Read each one's rationale on the clippy lint index-not just the fix.

1.5 Production Hardening Slice

  • Add a .cargo/config.toml that sets [profile.release] lto = "fat" and codegen-units = 1. Rebuild. Note compile-time cost vs. binary-size win.
  • Set up cargo-deny with a baseline deny.toml (license allowlist, advisory database). This file will grow each week.

Week 2 - Memory Layout: Stack, Heap, Data, BSS, TLS

2.1 Conceptual Core

  • A Rust value lives in exactly one of: a stack frame, the heap (via an allocator), .data (mutable static), .rodata (immutable static / const), .bss (zero-initialized static), or thread-local storage.
  • let x = 5; puts x on the stack. Box::new(5) puts the integer on the heap and a pointer on the stack. static X: i32 = 5; puts X in .rodata. static mut Y: i32 = 0; puts Y in .data. Internalize this taxonomy before writing another line of code.
  • const vs static: const is inlined at every use site (no address); static has a stable address. The distinction matters for FFI and for sharing across threads.

2.2 Mechanical Detail

  • std::mem::size_of, align_of, size_of_val, align_of_val. Run them on (), bool, &u8, &[u8], &dyn Debug, Box<dyn Debug>, Option<&T>, Option<Box<T>>. Predict the answers, then check.
  • Niche optimization: Option<&T> is 8 bytes, not 16, because the null pointer is the niche. Option<Box<T>> likewise. Learn to spot when you've broken the niche by adding a redundant variant.
  • #[repr(C)], #[repr(transparent)], #[repr(packed)], #[repr(align(N))]: when each is appropriate. Default #[repr(Rust)] reorders fields for tightest packing-this is observable via - -print=type-sizes` (nightly).
  • Thread-local storage: thread_local! macro, the OS-level cost (__tls_get_addr on Linux glibc), and why it is rarely the right answer.

2.3 Lab-"Layout Forensics"

Build a binary that allocates one value of each "kind": - a stack [u8; 64], - a Box<[u8; 64]>, - a static FOO: [u8; 64] = [0xAB; 64];, - a static mut BAR: [u8; 64] = [0; 64];, - a thread_local! RefCell<[u8; 64]>.

Print the address of each (&value as *const _ as usize). Run under cat /proc/self/maps (spawn cat from inside the program) and prove which segment each address falls in. Write up the mapping in NOTES.md.

2.4 Idiomatic & Clippy Drill

  • Lints to study: clippy::large_stack_arrays, clippy::large_enum_variant, clippy::box_collection, clippy::vec_box. Each maps to a layout pathology.

2.5 Production Hardening Slice

  • Set RUSTFLAGS="-C link-arg=-Wl,--print-memory-usage" (or use cargo-bloat) to inspect per-section binary footprint. Add cargo bloat --release --crates output to NOTES.md. This is the baseline for size-budget conversations later.

Week 3 - Ownership, Borrowing, and Region Inference

3.1 Conceptual Core

  • Ownership is destructor scheduling: the owner is the entity that will run Drop::drop. There is exactly one.
  • Borrowing is temporary capability delegation: &T grants read capability, &mut T grants exclusive read+write capability. A capability cannot outlive the resource that backs it (the lifetime constraint).
  • Lifetimes are not durations. They are region variables that the compiler infers under a system of inequality constraints ('a: 'b means region 'a outlives region 'b). The compiler does not know "how long" anything lives in seconds-only the partial order of regions.

3.2 Mechanical Detail

  • The three borrow-checker rules, stated formally:
  • At any program point, for any place p: at most one &mut p or any number of &p, never both.
  • References must be valid for their entire region.
  • The owner cannot mutate or move the value while a borrow is active (this is what NLL-non-lexical lifetimes-relaxed).
  • Two-phase borrows (v.push(v.len())): why this compiles even though it looks like aliasing.
  • Reborrowing: &mut *r produces a fresh &mut with a shorter lifetime. This is the foundation for passing &mut references into functions repeatedly.

3.3 Lab-"Defeat the Borrow Checker, Then Submit"

You will be given (as exercise files) ten programs that the borrow checker rejects. For each: 1. Predict which rule is violated before reading the diagnostic. 2. Fix it three different ways (e.g., scope shrinking, split borrow, Cell/RefCell). 3. Pick the idiomatic fix and justify it in a one-line comment-but only if the comment captures non-obvious reasoning. (See feedback rule on comments.)

3.4 Idiomatic & Clippy Drill

  • clippy::needless_lifetimes, clippy::redundant_clone, clippy::ptr_arg. The first two are about elision; the third is about API ergonomics.

3.5 Production Hardening Slice

  • Run cargo clippy --workspace --all-targets -- -D warnings in CI from week 1 forward. This is non-negotiable.

Week 4 - The Error Model

4.1 Conceptual Core

  • Rust has two error mechanisms, not one:
  • Result<T, E>-recoverable errors, encoded in the type system.
  • Panics-unrecoverable, stack-unwinding (or aborting) bugs.
  • ? is not exception handling. It is sugar for match plus From::from on the error variant. Internalize this; it is the difference between a clean error model and a re-implementation of Java exceptions.
  • panic = "abort" vs panic = "unwind": choose abort for binaries that own their process, unwind for libraries that may be embedded in a host that wants to catch (e.g., a Python extension).

4.2 Mechanical Detail

  • The shape of an idiomatic library error: an enum with #[non_exhaustive], thiserror::Error for derivation, and From impls for upstream errors.
  • The shape of an idiomatic application error: anyhow::Result at boundaries, typed errors internally. The split is deliberate: libraries owe their callers structured errors; applications owe their operators readable context.
  • Result::map_err and ? - chain ergonomics. The anti-pattern ofunwrap()outsidemain/tests/build.rs`.
  • #[track_caller]-the attribute that makes panic locations attribute to the caller rather than the panicking function. Why every helper that may panic should carry it.

4.3 Lab-"A Library With Two Faces"

Build parse-units: a small crate that parses strings like "3.5 GiB" into a structured Quantity. Requirements: - Public API returns Result<Quantity, ParseError> where ParseError is a thiserror enum with at least four variants. - Internally, use ? to compose. No unwrap allowed except in unit tests. - Provide a binary parse-units-cli that uses anyhow and prints rich context with .with_context(|| ...). - Ship 100% line coverage measured by cargo-llvm-cov.

4.4 Idiomatic & Clippy Drill

  • clippy::result_large_err (errors >128 bytes hurt the happy path), clippy::map_err_ignore, clippy::question_mark, clippy::unwrap_used, clippy::expect_used. Enable the last two as deny in libraries.

4.5 Production Hardening Slice

  • Add a panic = "abort" release profile and a panic = "unwind" test profile. Confirm the binary shrinks under abort. Add RUST_BACKTRACE=1 to your dev shell.
  • Wire up cargo audit and cargo deny check to CI. Both must pass on green main.

Month 1 Capstone Deliverable

A workspace foundations/ with three crates: 1. parse-units (week 4 lab) as a publishable library. 2. parse-units-cli as the application binary. 3. layout-forensics (week 2 lab) as an internal-only tool.

CI must run: cargo fmt --check, cargo clippy -D warnings, cargo test, cargo llvm-cov, cargo deny check, cargo audit. The workspace's release profile must enable lto = "fat", codegen-units = 1, panic = "abort", strip = "symbols". Document the resulting binary size in the workspace README.

Month 2-The Type System: Lifetimes, Traits, Smart Pointers, Drop

Goal: by the end of week 8 you can (a) write a function with higher-ranked trait bounds and explain why the bound is HRT, (b) predict from a type signature whether Box<T> or Rc<RefCell<T>> is appropriate, (c) design a public API that uses sealed traits to forbid downstream implementors, and (d) read and reason about variance.


Weeks

Week 5 - Advanced Lifetimes, Variance, and HRTBs

5.1 Conceptual Core

  • Subtyping in Rust exists only among lifetimes. 'static <: 'a for any 'a. There is no Liskov-style subtyping for nominal types.
  • Variance describes how the subtyping of a generic parameter lifts to the type constructor:
  • &'a T is covariant in 'a and T.
  • &'a mut T is covariant in 'a, invariant in T.
  • fn(T) -> U is contravariant in T, covariant in U.
  • Cell<T>, *mut T, UnsafeCell<T> are invariant in T.
  • Higher-Ranked Trait Bounds (HRTBs): for<'a> F: Fn(&'a str) -> &'a str - thefor<'a>quantifier means "for *every*'a` the caller might pick." Used pervasively in async closures and iterator combinators.

5.2 Mechanical Detail

  • The lifetime-elision rules (three of them; memorize). Then write a function that elision cannot solve and observe the error.
  • PhantomData<T>: how to force a type to "act as if" it owned a T for variance and Drop-checker purposes, even though no T is stored. Crucial for FFI wrappers and arena types.
  • 'static is not "lives forever"-it means "could live until program end if it wanted to." A &'static str is an immutable reference whose region has no upper bound.
  • GATs (Generic Associated Types): type Item<'a> where Self: 'a;. The pattern that finally unblocked lending iterators. Read RFC 1598 and the stabilization PR.

5.3 Lab-"A Lending Iterator"

Implement a WindowsMut lending iterator that yields overlapping &mut [T] windows over a slice. This requires GATs. Property-test it against a naive O(n²) reference implementation.

5.4 Idiomatic & Clippy Drill

  • clippy::needless_lifetimes, clippy::extra_unused_lifetimes, clippy::elidable_lifetime_names. Read RFC 2115 ("argument position impl Trait") and refactor a generic function to use APIT where the lifetime adds no information.

5.5 Production Hardening Slice

  • Add cargo-semver-checks to CI. Adding/removing a lifetime parameter to a public type is a SemVer-major change; the tool will catch it.

Week 6 - Traits, Coherence, and Monomorphization

6.1 Conceptual Core

  • A trait is a named set of capabilities. Implementing a trait for a type is a claim that the type satisfies a contract-the contract is partly enforced by the compiler and partly by the implementor's discipline (e.g., Hash and Eq consistency).
  • Coherence / orphan rule: an impl Trait for Type is allowed only if either Trait or Type is local to the current crate. This is what prevents "two crates impl the same trait for the same type" diamond conflicts.
  • Monomorphization: every distinct type substitution at a generic call site produces a fresh compiled function in the binary. Vec<u8>::push and Vec<i32>::push are two different symbols. This is the dual edge of the language: zero-cost abstraction in exchange for binary bloat and compile time.

6.2 Mechanical Detail

  • Static dispatch (impl Trait, generics) vs dynamic dispatch (dyn Trait). The vtable layout of dyn Trait: a fat pointer (data pointer + vtable pointer), where the vtable contains the destructor, size, alignment, and the trait method pointers. Inspect a real one with cargo asm.
  • Object safety / dyn compatibility: a trait is dyn-compatible if every method has a Self: Sized bound or takes self by reference and does not return Self or use Self in generic position. The 2024 edition formalized "dyn-compatible" terminology-use it.
  • Sealed traits: declare a private supertrait in a private module to prevent downstream impls. Used in std::error::Error historically, in tokio::io::AsyncRead extensions, and in any API where future-compatibility demands closed extension.
  • Auto traits (Send, Sync, Unpin, UnwindSafe, RefUnwindSafe): structural, opt-out via negative impl on nightly or PhantomData<*const ()> on stable.

6.3 Lab-"Bloat Forensics"

  1. Write a generic function fn process<T: Display>(items: &[T]) -> String that formats and concatenates. Instantiate it with five distinct types in a binary.
  2. Run cargo bloat --release --filter process and confirm there are five symbols.
  3. Refactor to a dyn Display version (&[&dyn Display]). Re-run cargo bloat. Document the binary-size delta and the codegen tradeoff.
  4. Now read the disassembly of the dyn version with cargo asm and identify the indirect call.

6.4 Idiomatic & Clippy Drill

  • clippy::needless_pass_by_value, clippy::trait_duplication_in_bounds, clippy::implicit_hasher. The last is subtle-explain why leaking RandomState into a public API is a SemVer hazard.

6.5 Production Hardening Slice

  • Configure [profile.release.package."*"] codegen-units = 1 for maximum cross-function inlining, while keeping [profile.dev] codegen-units = 256 for fast iteration. Document the build-time delta.

Week 7 - Smart Pointers and Interior Mutability

7.1 Conceptual Core

  • A "smart pointer" in Rust is a value that owns a heap allocation and customizes Drop. The std hierarchy:
  • Box<T>-unique ownership, single heap allocation.
  • Rc<T>-shared ownership, single-threaded, refcount.
  • Arc<T>-shared ownership, atomic refcount, thread-safe.
  • Weak<T> (paired with Rc/Arc)-non-owning observer; breaks cycles.
  • Interior mutability is the controlled violation of "no &mut while & exists." The valid mechanisms:
  • Cell<T>-get/set by value, no references handed out, single-threaded, zero overhead.
  • RefCell<T>-runtime borrow checking, panics on conflict, single-threaded.
  • Mutex<T> / RwLock<T>-runtime borrow checking with thread blocking, multi-threaded.
  • OnceCell<T> / OnceLock<T>-write-once, then immutable.
  • UnsafeCell<T>-the only primitive that legally permits interior mutability; everything above is built atop it.

7.2 Mechanical Detail

  • UnsafeCell<T> is the one type in the language for which &UnsafeCell<T> may be cast to *mut T without UB, provided the contents are not aliased mutably. Read the Rustonomicon chapter; it is the keystone.
  • The memory layout of Rc<T>: a RcBox<T> { strong: Cell<usize>, weak: Cell<usize>, value: T } allocated as one block. The Rc<T> is a NonNull<RcBox<T>>. Same for Arc<T> but with AtomicUsize and a separate cache line for the refcount on some impls (study triomphe for the alternative layout).
  • Reference cycles: Rc<RefCell<Node>> with two-way edges leaks. The Drop sequencing matters-when refcount reaches zero, the inner value is dropped before the allocation is freed, so cycles in Weak work.
  • Pin preview: introduced here only to flag that interior mutability + self-references is the combination that demands Pin. The full treatment is week 11.

7.3 Lab-"Build a Tracing Rc"

Implement TracingRc<T> from scratch using UnsafeCell and NonNull. It must: - Refcount strong and weak references correctly (study std::rc for the algorithm). - Log every clone/drop to a thread-local trace buffer. - Pass Miri (cargo +nightly miri test)-meaning your unsafe code is provably free of undefined behavior under the stacked-borrows model.

7.4 Idiomatic & Clippy Drill

  • clippy::rc_buffer, clippy::redundant_allocation, clippy::arc_with_non_send_sync, clippy::mut_from_ref. The last is a soundness lint-understand why it can never be silenced legitimately.

7.5 Production Hardening Slice

  • Run your TracingRc lab under cargo +nightly miri test. If it fails, fix the unsoundness; do not silence the diagnostic. Add miri to a nightly CI job as continue-on-error: false.

Week 8 - Drop, the Drop Checker, and Destructor Discipline

8.1 Conceptual Core

  • Drop::drop(&mut self) runs when a value goes out of scope, when mem::drop is called, when a panic unwinds, or when a Vec/etc. is dropped (each element is dropped in turn).
  • The Drop checker (dropck) is the part of the borrow checker that ensures a destructor cannot observe a borrowed value that is itself about to be dropped-the classic "owned Vec<&'a str> where the &'a str references something that will outlive 'a" hazard.
  • Drop order:
  • Local variables: reverse declaration order within a block.
  • Struct fields: declaration order.
  • Tuple elements: declaration order.
  • Closures: capture order.

8.2 Mechanical Detail

  • #[may_dangle] (nightly attribute, used in std internals): a hand-shake by which a Drop impl asserts it will not read its generic-parameter values during drop. This is what allows Vec<&'a T> to drop after &'a T's referent is gone, as long as Vec<T> does not access the Ts in its destructor.
  • PhantomData<T> and dropck: PhantomData<T> makes the dropck behave as if a T is owned. Necessary for FFI handles where the underlying C type needs lifetime-tracking even though Rust doesn't store one.
  • ManuallyDrop<T>-suppress the destructor entirely. Used for union fields and for transferring ownership across FFI without a double-free.
  • mem::forget-leak deliberately. Necessary when handing ownership to C code; constitutes a soundness escape hatch but not unsafe (because leaking is safe).
  • std::panic::catch_unwind: how unwinding interacts with destructors. A panic during a destructor's drop = abort (double-panic).

8.3 Lab-"Resource Acquisition Is Initialization"

Build a FileLock type wrapping flock(2): - On construction, acquire an advisory lock. - On Drop, release it. Even on panic. - Provide a try_lock constructor returning Result<FileLock, std::io::Error>. - Add a test that asserts the lock is released after a panic by spawning a child process that panics while holding the lock and observing in the parent that the lock can be re-acquired.

8.4 Idiomatic & Clippy Drill

  • clippy::mem_forget, clippy::drop_non_drop, clippy::let_underscore_must_use, clippy::unnecessary_struct_initialization. Drop discipline is one of the few areas where clippy is mostly about intent signaling.

8.5 Production Hardening Slice

  • Add a debug-build assertion that FileLock cannot be moved while held (sketch with Pin - full impl deferred to Week 11). Run the full month-2 workspace throughcargo +nightly miri test. Begin aMIRI_NOTES.md` log of every interaction the Miri output forces you to investigate; this becomes a study artifact.

Month 2 Capstone Deliverable

A type-system-lab/ workspace containing: 1. lending-iter (week 5)-a no_std - compatible lending iterator crate. 2.bloat-demo(week 6)-the static vs dynamic dispatch comparison, with a written tradeoff analysis. 3.tracing-rc(week 7)-Miri-clean. 4.flock-rs` (week 8)-a working file-lock RAII crate.

Workspace-level CI must add: cargo +nightly miri test, cargo semver-checks. Begin contributing minor doc fixes upstream (rust-lang/rust or a popular crate). Open at least one PR by end of month, however small.

Month 3-Concurrency and Async: Atomics, Lock-Free, Pin, Tokio, Smol

Goal: by the end of week 12 you can (a) implement a correct lock-free single-producer single-consumer queue with explicit memory orderings, (b) explain Pin<&mut Self> to a colleague using a self-referential generator example, (c) trace an await from source through state-machine codegen into a Tokio runtime poll, and (d) choose between Tokio, Smol, and embassy based on workload characteristics.


Weeks

Week 9 - Threading, Send, Sync, and the Memory Model

9.1 Conceptual Core

  • Send = "safe to transfer ownership to another thread."
  • Sync = "safe to share by &T across threads" (equivalently: &T: Send).
  • These are auto traits: structurally derived. A struct is Send if all its fields are Send, etc. Opt-out by including a PhantomData<*const ()> or by using !Send/!Sync negative impls (nightly).
  • Memory model: Rust's memory model is, today, the C++20 memory model (consume order excluded). You must internalize the happens-before relation, the meaning of Relaxed, Acquire, Release, AcqRel, SeqCst, and the difference between synchronization and visibility.

9.2 Mechanical Detail

  • std::thread::spawn vs thread::scope: scoped threads (stable since 1.63) allow borrowing from the parent stack frame because the join is guaranteed before the borrow ends. This is the right default for most parallelism.
  • Atomics (AtomicUsize, AtomicPtr<T>, AtomicBool, etc.): the API surface is small but every method takes an Ordering. The orderings are:
  • Relaxed-no synchronization; only atomicity. Used for counters where ordering doesn't matter (e.g., metrics).
  • Acquire / Release-pairwise synchronization. A Release store synchronizes with an Acquire load that observes the value, establishing happens-before from the store-side's prior writes to the load-side's subsequent reads.
  • AcqRel-both, on RMW (read-modify-write) operations.
  • SeqCst-total order across all SeqCst operations system-wide. Strongest, slowest, safest if you do not yet know what you're doing.
  • fence: standalone memory barriers. Rarely needed; usually a smell that the abstraction should be different.
  • Spin loops: std::hint::spin_loop() emits pause (x86) / yield (ARM). Critical for short-wait spinlocks; missing this hint hurts SMT performance and burns power.

9.3 Lab-"A Correct Spinlock"

Implement Spinlock<T> from scratch using AtomicBool: - lock() spins with Relaxed load, then Acquire CAS. - unlock() Release stores false. - Returns a SpinlockGuard<'_, T> whose Drop unlocks. - Verify with loom (run all interleavings-see week 10) that no two threads enter the critical section.

9.4 Idiomatic & Clippy Drill

  • clippy::mutex_atomic, clippy::mutex_integer, clippy::needless_collect, clippy::should_implement_trait. The first two flag the anti-pattern of Mutex<bool> where AtomicBool suffices.

9.5 Production Hardening Slice

  • Add a loom feature gate to your spinlock crate. Run cargo test --features loom in CI. Fail the build on any model-checker violation.

Week 10 - Channels, Lock-Free Patterns, and loom

10.1 Conceptual Core

  • Channel taxonomy:
  • MPSC (multi-producer, single-consumer): std::sync::mpsc, crossbeam_channel, tokio::sync::mpsc.
  • SPSC (single-producer, single-consumer): rtrb, ringbuf. Lock-free, often wait-free.
  • MPMC: crossbeam_channel, flume. Generally lock-free with backoff.
  • Broadcast: tokio::sync::broadcast. Many readers, lossy if slow.
  • Watch: tokio::sync::watch. Single-value, last-write-wins.
  • Lock-free vs wait-free:
  • Lock-free: at least one thread makes progress at any time.
  • Wait-free: every thread completes in bounded steps. Wait-free is much harder; most real-world "lock-free" code is lock-free, not wait-free.
  • The ABA problem: a CAS that compares a pointer can succeed even though the pointer was freed and re-allocated to the same address. Solutions: hazard pointers, epoch-based reclamation (crossbeam-epoch), or tagged pointers.

10.2 Mechanical Detail

  • crossbeam-epoch for safe lock-free memory reclamation. Read its source.
  • Loom: a model checker that exhaustively explores thread interleavings of programs written against its loom::sync::* shims. Used by Tokio and others to validate concurrent data structures. Read the loom user guide and the tokio test suite.
  • Cache effects: false sharing. Pad hot atomics to 64 bytes (or 128 on Apple Silicon) with crossbeam_utils::CachePadded.
  • Backoff strategies: exponential with jitter, then thread::yield_now(), then park. Read crossbeam_utils::Backoff.

10.3 Lab-"An SPSC Ring Buffer"

Implement a fixed-capacity SPSC ring buffer: - Two AtomicUsize indices (head, tail), each on its own cache line. - push and pop use Acquire/Release ordering pairs. - Validate under loom with at least 4 elements and 3 pushes/pops. - Benchmark against rtrb with criterion. You should be within 2× on x86_64.

10.4 Idiomatic & Clippy Drill

  • clippy::needless_lifetimes, clippy::missing_const_for_fn. Note that lock-free primitives often cannot be const fn due to atomic init constraints-explain when.

10.5 Production Hardening Slice

  • Add RUSTFLAGS="-Z sanitizer=thread" (nightly) to a CI job. Run your SPSC tests under TSan in addition to Loom. The two catch overlapping but distinct classes of bugs.

Week 11 - Async Foundations: Future, Pin, Unpin, the State Machine

11.1 Conceptual Core

  • A Future is a state machine with a poll method that returns Poll::Ready(T) or Poll::Pending. await is the only legal way to drive a future from inside another future.
  • async fn desugars to a function returning an anonymous type that implements Future. The compiler synthesizes the state machine from the control flow of the async block.
  • Pin<P> is a wrapper around a pointer P that promises the pointee will not be moved (in memory) until it is dropped-unless the pointee is Unpin. This is the linchpin that lets self-referential async state machines work.

11.2 Mechanical Detail

  • Why Pin? When an async function awaits, the compiler stores all locals (including references to other locals) in a single struct-the state. References to other fields of the same struct are self-references, valid only while the struct does not move. Without Pin, a Vec::push that re-allocates the state would invalidate them. Pin encodes the move-prohibition in the type system.
  • Unpin is an auto trait: a type is Unpin if it is safe to move even when pinned. Most types are Unpin. The exceptions: generators, async blocks, intrusive list nodes, self-referential structs.
  • pin! macro (stable since 1.68): pin a value on the stack ergonomically. Replaces tokio::pin! for std users.
  • The Waker: an opaque handle the runtime hands to a future so the future can signal "I'm ready to be polled again." Wakers are Clone + Send + Sync and have a wake() method.
  • Cancellation in async Rust = dropping the future. Every await point is a cancellation point. Code that holds a lock across an await and then cancels can deadlock if the lock is not async-aware. This is the single most common source of async bugs in production.

11.3 Lab-"An Async Channel From Scratch"

Implement a single-shot async oneshot channel: - Sender<T> has send(self, T). - Receiver<T> is Future<Output = Result<T, Cancelled>>. - Use a single Mutex<State> and a Waker slot. - Test with both tokio::test and smol::block_on. The result must be runtime-agnostic.

11.4 Idiomatic & Clippy Drill

  • clippy::async_yields_async, clippy::large_futures, clippy::unused_async. The middle lint flags futures whose state struct exceeds a kilobyte-a real performance hazard once you spawn millions.

11.5 Production Hardening Slice

  • Run your oneshot under tokio-console and console-subscriber. Confirm the future is dropped promptly on cancellation. Add tracing::instrument to every public async fn. Document the size of the generated futures with cargo +nightly rustc -- -Z print-type-sizes.

Week 12 - Runtimes: Tokio Internals, Smol, Embassy

12.1 Conceptual Core

  • A runtime is the executor + reactor + I/O driver that actually polls futures. The Future trait is part of core; runtimes are external.
  • Executor = the part that picks which task to poll next.
  • Reactor = the part that registers I/O readiness with the OS (epoll/kqueue/IOCP/io_uring).
  • Task = a top-level future plus its scheduling metadata. Tasks are 'static.

12.2 Mechanical Detail-Tokio

Read the Tokio source tree in this order: 1. tokio/src/runtime/scheduler/multi_thread/ - the work-stealing scheduler. Each worker has a local LIFO/FIFO hybrid queue plus a global injection queue. Steals from peer queues on starvation. 2.tokio/src/runtime/io/driver.rs - wraps mio (which wraps epoll/kqueue/IOCP). I/O readiness wakes the relevant task's Waker. 3. tokio/src/runtime/time/ - hashed wheel timer fortokio::time::sleep. 4.tokio/src/sync/notify.rsandmutex.rs - async-aware synchronization. Note the intrusive linked list of waiters.

12.3 Mechanical Detail-Smol & async-std

  • Smol's executor (async-executor) is a simpler, single-file reactor. Read it; you can understand the entire stack in an afternoon.
  • async-std is now in maintenance; mention only for historical context.
  • embassy for embedded: an executor that runs on bare-metal Cortex-M without an OS. Uses interrupt-driven wakers.

12.4 Lab-"Roll-Your-Own Mini Executor"

Build a single-threaded executor in ~150 lines: - A VecDeque<Arc<Task>> ready queue. - Task holds a Mutex<Pin<Box<dyn Future>>> and implements ArcWake (or Wake on stable). - block_on polls the root future; auxiliary spawn adds tasks. - Run a small TCP echo server on top using polling (the same crate Smol uses) for I/O.

12.5 Idiomatic & Clippy Drill

  • clippy::redundant_async_block, clippy::manual_async_fn, clippy::should_panic_without_expect. Read the Tokio style guide and adopt its task-naming conventions.

12.6 Production Hardening Slice

  • For your mini-executor, add a tracing subscriber and a panic hook that aborts the process (so a panicked task does not silently disappear). This is the same pattern Tokio's unhandled_panic = "shutdown_runtime" enables-set it on every Tokio runtime you create.

Month 3 Capstone Deliverable

A concurrency-lab/ workspace: 1. spinlock-rs (week 9)-Loom-verified. 2. spsc-ring (week 10)-Loom + TSan + Criterion benches. 3. oneshot-rs (week 11)-runtime-agnostic, Miri-clean. 4. mini-exec (week 12)-under 200 LoC, runs the TCP echo demo.

CI gates: Loom, Miri, TSan (nightly), Criterion regression tracking. Add a one-page architectural ADR (Architectural Decision Record) for each crate explaining the ordering choices. ADRs become source-of-truth artifacts in subsequent months.

Month 4-Unsafe Rust, FFI, and Macros

Goal: by the end of week 16 you can (a) write an unsafe fn whose safety contract is documented well enough to survive a security review, (b) bind to a non-trivial C library and also expose a Rust library to C consumers, (c) write a macro_rules! that respects hygiene and can be re-imported, and (d) ship a procedural macro that derives a non-trivial trait with custom attributes and useful diagnostics.


Weeks

Week 13 - Unsafe Rust: Raw Pointers, NonNull, MaybeUninit, UB

13.1 Conceptual Core

  • unsafe is not "turn off the borrow checker." It unlocks five extra capabilities:
  • Dereference raw pointers.
  • Call unsafe fns.
  • Implement unsafe traits.
  • Access static mut (now discouraged in favor of SyncUnsafeCell).
  • Access fields of unions.
  • The borrow checker, lifetime checker, type checker-all still run. unsafe widens ability, it does not weaken checks.
  • The discipline is safety contracts: every unsafe fn and every unsafe { ... } block must be paired with a comment articulating the invariants the caller is asserting. The community standard is // SAFETY: ... comments, scanned by clippy::undocumented_unsafe_blocks.

13.2 Mechanical Detail

  • *const T vs *mut T-the variance differs (*mut T is invariant in T), the legality of forming references differs (you may not produce &T from a *const T aliasing a &mut T), but otherwise they behave the same. The mut/const distinction is documentation, not enforcement.
  • NonNull<T>-a wrapper around *mut T with the niche optimization (the null bit pattern is forbidden). Use it for FFI handles and as the storage primitive in Box/Rc/Arc.
  • MaybeUninit<T>-the way to manipulate uninitialized memory legally. mem::uninitialized is deprecated for soundness reasons; MaybeUninit is the replacement. Internalize MaybeUninit::write, assume_init, assume_init_ref.
  • Provenance: a pointer carries not just an address but a provenance tag indicating which allocation it derives from. ptr::with_addr and ptr::map_addr are the safe ways to manipulate addresses without losing provenance. Read the strict-provenance proposal (std::ptr module docs).
  • The Rustonomicon's UB list: dangling references, null references, misaligned references, mutable aliasing, type confusion (transmuting padding), data races. Memorize.

13.3 Lab-"A Sound Vec"

Re-implement Vec<T> from scratch (the Nomicon's chapter 9 walk-through is the reference). Requirements: - RawVec allocator wrapper handling growth. - ZST (zero-sized type) handling-Vec<()> must work without ever allocating. - Drop correct under panic in T::drop. - Iteration via IntoIter with proper drop on partial consumption. - Pass Miri on every public method.

13.4 Idiomatic & Clippy Drill

  • clippy::undocumented_unsafe_blocks, clippy::multiple_unsafe_ops_per_block, clippy::transmute_ptr_to_ref, clippy::cast_ptr_alignment. Each maps to a documented UB class.

13.5 Production Hardening Slice

  • Run your Vec lab under cargo +nightly miri test -Zmiri-strict-provenance. Document each Miri diagnostic and the fix in a SAFETY_LOG.md. This document is the deliverable, not the code.

Week 14 - FFI: Calling C, Being Called By C

14.1 Conceptual Core

  • The C ABI is the lingua franca. Rust can both consume it (extern "C" fn declarations) and expose it (#[no_mangle] pub extern "C" fn).
  • Two directions of pain:
  • Calling C from Rust-bindings, header parsing (bindgen), null-pointer discipline, CStr/CString lifetimes, errno.
  • Calling Rust from C-symbol mangling, panic discipline (panics across FFI = UB), opaque pointers, version-stable headers (cbindgen).

14.2 Mechanical Detail

  • bindgen: parses C headers via libclang, emits Rust extern declarations. Configure via wrapper.h and build.rs. Common pitfalls: bitfield representation, function-like macros (not handled), inline functions (must be static-libbed separately).
  • cbindgen: the inverse. Reads your Rust source and emits a C header. Requires a cbindgen.toml. Generated headers should be checked into include/ and CI-verified.
  • #[repr(C)] is mandatory for any type crossing the boundary. #[repr(transparent)] for newtype wrappers around FFI primitives.
  • Strings: CStr is unsized borrowed, CString is owned. Never assume a *const c_char is UTF-8-convert through CStr::from_ptr and then to_str().
  • Panic safety across FFI: panics that unwind across an extern "C" boundary are UB. Wrap every extern "C" fn body in std::panic::catch_unwind and convert panics to error codes, or compile with panic = "abort".
  • errno: thread-local in glibc; access via std::io::Error::last_os_error() immediately after a syscall.

14.3 Lab-"Bind a Real C Library and Expose a Rust One"

Two parts: 1. Consume: write Rust bindings to libsodium's crypto_secretbox family. Use bindgen for the raw layer, then wrap in safe Rust (own the keys with Zeroizing<[u8; 32]>, use typed nonces, return Results). 2. Expose: take your parse-units crate from Month 1 and ship a C-callable parse_units_c library with a cbindgen - generated header. Provide aMakefile` that links a tiny C program against it.

14.4 Idiomatic & Clippy Drill

  • clippy::not_unsafe_ptr_arg_deref, clippy::missing_safety_doc, clippy::fn_to_numeric_cast_any, clippy::transmute_ptr_to_ptr. The first two are FFI-specific safety doc enforcers.

14.5 Production Hardening Slice

  • Cross-compile your parse-units C library to aarch64-unknown-linux-gnu and x86_64-unknown-linux-musl. Verify the resulting .so/.a artifacts with nm and readelf. Add this to CI as a matrix build.

Week 15 - Declarative Macros (macro_rules!)

15.1 Conceptual Core

  • macro_rules! is a pattern-matching macro system operating on token trees. It is hygienic by default-identifiers introduced in the macro do not collide with identifiers in the call site (with subtle exceptions you must learn).
  • Designators: expr, ident, ty, pat, tt, block, stmt, path, meta, vis, lifetime, literal, item. Each constrains what tokens the matcher accepts and how they may be re-emitted.
  • Repetition: $( ... )*, $( ... )+, $( ... ),*. The macro author is responsible for handling empty/non-empty cases.

15.2 Mechanical Detail

  • Hygiene exceptions: $crate is the canonical way to refer to the defining crate. Without it, vec![1,2,3] would break if the user shadowed std::vec. Use $crate:: in every path emitted by a macro.
  • Recursion: macros may invoke themselves. The recursion limit is #![recursion_limit = "256"] by default-raise it explicitly if needed (and document why).
  • TT munching: a pattern where the macro consumes tokens one at a time recursively. The standard idiom for parsing custom DSLs in macro_rules!.
  • Re-export discipline: #[macro_export] makes a macro visible at the crate root. The 2018+ edition allows pub use of macros, which is the modern preferred path.

15.3 Lab-"A hashmap! Macro With Diagnostics"

Implement a hashmap! macro: - hashmap! { "a" => 1, "b" => 2 } produces a HashMap. - Trailing comma allowed. - Type-checks: a typo like hashmap! { "a" => 1, "b" -> 2 } should produce a useful error pointing at the bad token (use compile_error! strategically). - Pre-allocates with HashMap::with_capacity.

15.4 Idiomatic & Clippy Drill

  • clippy::crate_in_macro_def (use $crate::), clippy::single_call_fn, clippy::useless_format. Read the Little Book of Rust Macros sections on hygiene and TT munching.

15.5 Production Hardening Slice

  • Add cargo-expand to your dev tooling. Inspect the expansion of every macro you ship; commit a sample expansion as a doctest. This is your guard against silent semantic drift in macro maintenance.

Week 16 - Procedural Macros

16.1 Conceptual Core

  • Procedural macros are compiled crates of type proc-macro that take TokenStream in and return TokenStream out. Three flavors:
  • #[derive(MyTrait)]-extends a struct/enum with trait impls.
  • Attribute macros (#[my_attr])-wholesale rewrite of an item.
  • Function-like (my_macro!(...))-like macro_rules! but with arbitrary computation.
  • The toolchain: proc-macro2 (a re-export wrapper that allows non-proc-macro testing), syn (parser), quote (token-stream building via quote! {}).

16.2 Mechanical Detail

  • Spans: every token carries a Span recording its source location. To produce error messages that point at user code (not macro code), preserve spans across transformations. syn::Error::new_spanned(&item, "msg").to_compile_error() is the idiom.
  • Hygiene in proc macros: weaker than macro_rules!. By default, identifiers are call-site hygiene; you must explicitly use Span::mixed_site() for hygiene-sensitive identifiers.
  • Diagnostics: stable proc macros emit errors via compile_error! token streams. Nightly has proc_macro::Diagnostic with structured suggestions (used by serde on nightly).
  • Testing: trybuild for compile-fail tests; cargo expand for snapshot tests (insta works well here).

16.3 Lab-"All Three Flavors"

Build the dtolnay/proc-macro-workshop exercises end-to-end: 1. derive_builder - derive a builder pattern with field-level attributes for renaming and each-element setters. 2.seq - function-like macro seq!(N in 0..8 { ... }) that emits N expansions. 3. `sorted - attribute macro that enforces enum-variant or match-arm sortedness with proper spans on errors.

This workshop is the gold standard for proc-macro pedagogy. Do all of it.

16.4 Idiomatic & Clippy Drill

  • Proc-macro crates have their own pitfalls: avoid panicking in your macro (always emit compile_error!), avoid pulling in heavy deps (every dep slows downstream builds), and feature-gate proc-macro2's nightly feature for span fidelity.

16.5 Production Hardening Slice

  • Add trybuild UI tests. Pin the toolchain (UI test snapshots are toolchain-sensitive). Add cargo expand snapshot tests via insta. Both are in CI; both must pass on nightly and the pinned stable.

Month 4 Capstone Deliverable

A unsafe-ffi-macros/ workspace: 1. mini-vec (week 13)-Miri-clean re-implementation of Vec. 2. safe-sodium (week 14)-bindgen + cbindgen, cross-compiled. 3. hashmap-macro (week 15)-published with a doctest of cargo expand output. 4. derive-workshop (week 16)-all three proc-macro-workshop exercises.

CI gates additions: cargo expand - based snapshot tests,trybuildUI tests, cross-compilation matrix (x86_64-unknown-linux-musl, aarch64-unknown-linux-gnu, x86_64-pc-windows-gnu). Open at least one PR against a real crate fixing anunsafeblock's missing SAFETY comment (clippy::undocumented_unsafe_blocks` will surface candidates in the ecosystem).

Month 5-Production Architecture: Hexagonal, Zero-Copy I/O, Observability, Testing

Goal: by the end of week 20 you can (a) lay out a non-trivial service following hexagonal/ports-and-adapters and justify each boundary, (b) move bytes from a socket through a parser into application state without copying, (c) instrument a service end-to-end with tracing and OpenTelemetry, and (d) ship a test suite combining unit, integration, property-based, and fuzz harnesses.


Weeks

Week 17 - Hexagonal Architecture and Domain Modeling in Rust

17.1 Conceptual Core

  • Hexagonal (ports-and-adapters) treats the domain as the center, with explicit ports (traits) describing the interactions the domain needs from the outside world, and adapters (concrete impls) plugging in real I/O. The compiler enforces the boundary; the trait is the contract.
  • Rust is unusually good for hexagonal architecture: trait objects (or generics) at the seam, Result for error propagation, cargo workspaces for crate-level enforcement of dependency direction.
  • Type-driven design: make illegal states unrepresentable. A User whose email might be unverified is a different type from a VerifiedUser. This is the New Type pattern operating at scale.

17.2 Mechanical Detail

  • Workspace layout for a hexagonal service:
    service/
      crates/
        domain/          # pure types and traits, no I/O deps
        application/     # use cases, depend on domain
        adapters/
          postgres/      # impl domain ports against sqlx
          kafka/         # impl event-bus port
          http/          # impl HTTP-handler port using axum
        bin/
          api/           # wires adapters into a runnable binary
    
    Each adapter crate depends on domain and nothing else from this workspace. Enforce with cargo-deny [bans] rules and cargo modules graph checks.
  • Ports as traits: prefer async_trait only when needed (allocation per call). When the trait is single-impl per binary, use a generic parameter Service<Repo: UserRepo> to avoid the Box<dyn> cost.
  • Error boundaries: domain errors are typed enums; adapter errors map to domain errors at the seam. Never let a sqlx::Error leak to the HTTP handler-it's a layering violation and a security risk (it leaks schema details).

17.3 Lab-"A Hexagonal URL Shortener"

Build a workspace implementing a URL shortener: - domain crate: ShortUrl, UrlAlias newtypes, a UrlRepository trait. - application crate: Shorten, Resolve use cases. - adapters/postgres crate: implements UrlRepository with sqlx. - adapters/http crate: axum handlers using the application layer. - bin/api crate: composition root. - An adapters/in-memory crate used by tests, so application logic is testable without a database.

17.4 Idiomatic & Clippy Drill

  • clippy::module_name_repetitions, clippy::too_many_lines, clippy::struct_field_names. These shape API ergonomics, not correctness-but in a hexagonal codebase, ergonomics is maintainability.

17.5 Production Hardening Slice

  • Add cargo deny check bans rules forbidding domain from depending on any I/O crate (tokio, sqlx, reqwest, etc.). The CI must fail on a violation. This is the architectural test.

Week 18 - Zero-Copy I/O and the Poll-Based Model

18.1 Conceptual Core

  • Zero-copy in the small means avoiding memcpy between buffers. In the large it means retaining the same allocation from kernel boundary through parsing into application data structures.
  • The poll-based model (epoll/kqueue/IOCP) returns readiness, not data. The application reads when ready, into a buffer it owns. This is the model mio exposes; tokio builds on top of it.
  • io_uring is the alternative completion-based model on modern Linux: the application submits a request, the kernel performs it and signals completion. Better throughput at high QPS; integrating it cleanly with Rust's borrow model is non-trivial (tokio-uring, glommio, monoio).

18.2 Mechanical Detail

  • bytes::Bytes and BytesMut: refcounted byte buffers that support cheap slicing and splitting. Bytes::slice produces a new Bytes that points into the same allocation. The cornerstone of zero-copy parsing pipelines.
  • AsyncRead/AsyncWrite (Tokio): the async analogue of Read/Write. The tokio variants take a ReadBuf to allow zero-init buffers; the futures-rs variants (used by Smol) use &mut [u8].
  • Vectored I/O: readv/writev. IoSlice and IoSliceMut in std. Avoids small-write coalescing copies.
  • sendfile(2) and splice(2): kernel-mediated copy avoidance for proxy workloads. Wrappers exist in nix and rustix.
  • Parsers that borrow from input: nom and winnow produce &'a [u8] references into the source buffer. serde with #[serde(borrow)] on &'a str fields. Combine with Bytes to keep allocations alive.

18.3 Lab-"A Zero-Copy Line Protocol"

Build a server speaking a minimal newline-delimited protocol: - Read into a BytesMut with try_read_buf. - Parse line-by-line with winnow, yielding &[u8] slices. - Push each parsed message into a downstream channel as a Bytes (cloned cheaply, shared with the parser's allocation). - Benchmark with wrk or tcpkali. Inspect with perf and confirm __memcpy is not a hot frame.

18.4 Idiomatic & Clippy Drill

  • clippy::read_zero_byte_vec, clippy::unbuffered_bytes, clippy::needless_collect. Each is a hint that you're allocating where a `Bytes - style flow would suffice.

18.5 Production Hardening Slice

  • Add tokio-console instrumentation. Set tokio_unstable in RUSTFLAGS (only in dev/profile builds). Run a 60-second load test and capture a perf record flamegraph. Commit the SVG.

Week 19 - Observability: tracing, metrics, OpenTelemetry

19.1 Conceptual Core

  • Three pillars: logs, metrics, traces. In Rust, the de-facto crates are tracing (logs+spans), metrics (counters/gauges/histograms), and opentelemetry (export to OTLP collectors).
  • Spans are the structured-logging analog of stack frames: a span represents a unit of work, may contain child spans, and carries fields. tracing instruments your code with macros.
  • Subscriber is the consumer of spans/events: pretty-printer, JSON logger, Jaeger/OTLP exporter, or a custom sink. Subscribers compose via Layers.

19.2 Mechanical Detail

  • Replace log - crate users withtracing(thelog - compat shim is adequate). Place #[tracing::instrument] on every async use case in the application layer. Never on tight loops in hot paths-it has overhead.
  • tracing-subscriber setup: EnvFilter for runtime log-level control; fmt::Layer for stdout; opentelemetry::tracing layer for distributed tracing.
  • Metrics: metrics facade + metrics-exporter-prometheus for /metrics scraping. Counters for events, gauges for resource levels, histograms for latencies. Use Histogram not `Summary - Prometheus aggregation matters.
  • Cardinality discipline: a label like user_id has unbounded cardinality and will OOM Prometheus. Tag with tenant_id, endpoint, status_class only.

19.3 Lab-"Add Observability to the Hexagonal URL Shortener"

Take week 17's URL shortener and add: - tracing::instrument on every use case, with explicit fields (no PII). - Prometheus /metrics endpoint with request counts and per-endpoint latency histograms. - OTLP export to a local Jaeger via docker-compose. - A flamegraph.svg from a 30-second load test, committed.

19.4 Idiomatic & Clippy Drill

  • clippy::print_stdout, clippy::print_stderr, clippy::dbg_macro. Production code logs through tracing; these lints catch slip-ups.

19.5 Production Hardening Slice

  • Configure log/metric redaction at the subscriber layer. Demo: a request body containing an email address must not appear in logs. Add a regex-based redaction layer; unit-test it. This is a compliance prerequisite.

Week 20 - Testing Strategy: Unit, Property, Fuzz, Miri, Integration

20.1 Conceptual Core

  • A production Rust codebase has five test surfaces:
  • Unit (#[test] in mod tests)-fast, in-crate, mock-free where possible.
  • Integration (tests/ directory)-public-API only, one binary per file.
  • Property-based (proptest, quickcheck)-invariants, not examples.
  • Fuzzing (cargo-fuzz with libFuzzer, or afl.rs)-adversarial inputs.
  • Model checking / Miri-concurrency interleavings and UB detection.
  • Each surface answers a different question. Skipping one leaves a class of bugs uncovered.

20.2 Mechanical Detail

  • proptest: shrinking-aware property testing. proptest! { #[test] fn round_trip(x in 0..1000u32) { ... } }. Use it for serialization round-trips, parsers, sort/search.
  • cargo-fuzz: cargo +nightly fuzz init && cargo +nightly fuzz add my_target. Each target is a fuzz_target!(|data: &[u8]| { ... }). Run continuously on a CI runner, store the corpus.
  • arbitrary crate: derive Arbitrary for your domain types so fuzz inputs are well-typed.
  • insta for snapshot tests: golden-file testing for proc-macro expansions, Debug outputs, serialized formats.
  • testcontainers: spin up Postgres/Kafka/Redis from inside #[tokio::test]. The right tool for adapter integration tests in a hexagonal codebase.

20.3 Lab-"Test-Pyramid the URL Shortener"

  • Property-test the alias-generation function (idempotent, collision-resistant under birthday-bound assumptions).
  • Fuzz the public HTTP handlers via the axum::Router directly (no socket).
  • Integration-test the Postgres adapter against a real Postgres in testcontainers.
  • Snapshot-test the OpenAPI spec with insta.
  • Achieve 90%+ coverage per cargo-llvm-cov.

20.4 Idiomatic & Clippy Drill

  • clippy::should_panic_without_expect, clippy::panic_in_result_fn, clippy::tests_outside_test_module.

20.5 Production Hardening Slice

  • Add a continuous-fuzzing job (e.g., on a scheduled GitHub Action). Persist the corpus as an artifact. Any new crash file is a P0 issue; document the triage flow in SECURITY.md.

Month 5 Capstone Deliverable

A production-shaped url-shortener-prod/ workspace: - Hexagonal layout with adapter isolation enforced by cargo deny. - Zero-copy parser on the wire. - tracing + Prometheus + Jaeger. - Five test surfaces wired into CI. - A one-page RUNBOOK.md describing alarms, dashboards, and rollback procedures.

This is the first artifact in the curriculum that resembles a real production service. Treat it as your portfolio piece.

Month 6-Mastery: Custom Data Structures, no_std, Compiler Internals, Capstone

Goal: by the end of week 24 you have shipped one capstone deliverable in your chosen track (compiler / fintech / kernel) and can defend every design decision in a senior-level technical interview.


Weeks

Week 21 - Implementing Complex Data Structures From Scratch

21.1 Conceptual Core

  • The std collections (Vec, HashMap, BTreeMap, VecDeque) are excellent but generic. Real systems regularly need: lock-free hash tables, B-trees specialized to a key shape, intrusive linked lists, slab allocators, MPMC queues with bounded backpressure, log-structured merge trees, skip lists.
  • Building these from scratch once teaches the patterns: cache-friendly layout, intrusive vs extrusive, the cost of Box, the role of NonNull in linked structures.

21.2 Mechanical Detail-three target structures

(a) A B-Tree map. - Branching factor ~6 by default in std; tune for your key/value sizes. - Inner nodes store keys + child pointers; leaf nodes store keys + values. - Fixed-capacity arrays ([MaybeUninit<K>; B]) avoid per-key allocations. - The split/merge invariants are the entire algorithmic content; the Rust difficulty is expressing the invariants in safe code.

(b) A lock-free concurrent hash map. - Study dashmap (sharded, simpler) and flurry (Java-ConcurrentHashMap port using crossbeam-epoch). Build a sharded version yourself first; only attempt epoch-based after week 22's allocator work. - Sharding: N shards, each a RwLock<HashMap<K, V>>. Hash key, mod-shard, lock the shard. Trivially correct, scales linearly until shard contention. - Lock-free: open addressing with atomic CAS on slots, epoch-based reclamation for resizes. This is hard.

(c) An intrusive doubly-linked list. - The Linux kernel pattern: nodes embed prev/next pointers, list operations are O(1) and zero-allocation. - In Rust, this requires Pin (nodes can't move once linked) and careful unsafe code. Read intrusive-collections and tokio::sync::notify's waiter list as references.

21.3 Lab-"Pick One and Ship It"

Implement one of the three to publishable quality: - Property-tested against std equivalent. - Miri-clean. - Loom-verified (for the lock-free). - Criterion-benchmarked against std/dashmap/intrusive-collections. - README explains the algorithmic choice and tradeoffs.

21.4 Idiomatic & Clippy Drill

  • clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks, clippy::pedantic group. By now these should fire rarely.

21.5 Production Hardening Slice

  • Publish to a private registry (or a personal GitHub Packages registry). Tag a v0.1.0. Run cargo semver-checks against the tag from this point on.

Week 22 - no_std, Custom Allocators, Embedded Targets

22.1 Conceptual Core

  • #![no_std] removes the std prelude and the default allocator. Code becomes runnable on bare metal (no OS), in kernels, in WASM-without-runtime, and in embedded MCUs.
  • core (no allocator) and alloc (allocator-required) are the layers below std. alloc provides Box, Vec, String, etc., but requires you to nominate a #[global_allocator].
  • Custom allocators: implement core::alloc::GlobalAlloc (for the global one) or the unstable Allocator trait (for per-collection allocators, on nightly).

22.2 Mechanical Detail

  • Cross-compilation: rustup target add thumbv7em-none-eabihf (Cortex-M4F), rustup target add riscv64gc-unknown-linux-gnu, rustup target add wasm32-unknown-unknown. cargo build --target=... and the right linker via .cargo/config.toml.
  • Embedded toolchain: cargo-binutils, probe-rs for flashing, cortex-m, embedded-hal traits, rtic or embassy for concurrency. The embedded ecosystem is its own discipline; the goal here is fluency, not specialization.
  • Allocators worth knowing: mimalloc, jemalloc (tikv-jemallocator), snmalloc, bumpalo (arena), linked_list_allocator (no_std heap). Switching the global allocator is one line in main.rs.
  • Panic in no_std: you must provide a #[panic_handler]. Common implementations: spin forever, write to a UART, reset the chip.

22.3 Lab-"Two Targets"

  • Bare metal: blink an LED on a real or QEMU-emulated Cortex-M target using embassy. Optional but recommended.
  • Custom allocator: write a simple bump allocator. Use it as #[global_allocator] for a small no_std + alloc benchmark and observe behavior.

22.4 Idiomatic & Clippy Drill

  • clippy::std_instead_of_alloc, clippy::std_instead_of_core, clippy::alloc_instead_of_core. These force the discipline of leveling imports correctly-a no_std library that imports from std will not compile downstream.

22.5 Production Hardening Slice

  • Add a CI matrix entry for cargo build --target=thumbv7em-none-eabihf --no-default-features. Library crates should compile in no_std mode (gated by a feature flag)-this is a real selling point and a frequent regression source.

Week 23 - Compiler Internals: MIR, Borrow Check, Codegen

23.1 Conceptual Core

  • The Rust compiler is query-based: every piece of derived information (types, traits, MIR, codegen units) is computed lazily and memoized in a query system (rustc_query_system).
  • MIR (Mid-level IR) is the IR on which borrow checking, optimization, and constant evaluation run. It is a CFG of basic blocks with statements and terminators, much closer to LLVM IR than HIR.
  • Bootstrap: rustc is written in Rust. To build it from source you compile a stage-0 (downloaded prebuilt) → stage-1 (built by stage-0) → stage-2 (built by stage-1, the deliverable). Understanding bootstrap is half the battle of contributing.

23.2 Mechanical Detail

  • Clone rust-lang/rust, run ./x.py setup (choose compiler or library profile), ./x.py build library/std. Plan ~30 minutes for the first build. Subsequent incremental builds are ~minutes.
  • Read rustc-dev-guide.rust-lang.org cover-to-cover. The high-yield chapters: "Overview of the compiler", "Queries", "MIR", "Borrow checking", "Trait resolution".
  • rustc -Z unpretty=mir to dump MIR; rustc -Z unpretty=hir for HIR. Read the MIR of a small program with a borrow-check error-the diagnostics make sense once you see the IR.
  • The E-easy and E-mentor labels in the rust-lang/rust issue tracker: the on-ramp. Pair with rustc-dev-guide's "your first PR" walkthrough.

23.3 Lab-"Read, Build, Land"

  1. Build rustc from source. Modify a single diagnostic message in compiler/rustc_borrowck/src/... to add a new help line. Rebuild stage-1 and confirm the new message in - -explain`.
  2. Find an issue with E-easy. Read the linked discussion. Cross-reference with rustc-dev-guide. Do not yet open a PR; instead, write a one-page plan describing the proposed change. Discuss with a maintainer in the issue comments.

23.4 Idiomatic & Clippy Drill

  • The rustc codebase has its own rustc_lint lints. Read compiler/rustc_lint_defs/src/builtin.rs to see how lints are defined; this is the same machinery clippy uses.

23.5 Production Hardening Slice

  • Configure your shell with rust-analyzer.checkOnSave.command = "clippy". Adopt cargo +nightly fmt -- --config-path rustfmt.toml. The compiler repo enforces its own rustfmt config; using the same locally avoids surprise CI failures.

Week 24 - Capstone Integration, Profiling, Hardening, Defense

24.1 Conceptual Core

  • The final week is integration, not new material. Bring the chosen capstone (see CAPSTONE_PROJECTS.md) to merge-ready quality: profile, tune, document, and prepare to defend the design.

24.2 Mechanical Detail-Profiling Toolkit

  • perf + flamegraphs (flamegraph crate or cargo flamegraph): the daily driver for CPU time analysis on Linux.
  • samply: a perf record alternative producing Firefox-Profiler-compatible output. Lower friction.
  • heaptrack for allocator profiling. dhat-rs for in-process heap snapshots in tests.
  • cargo-llvm-lines: which generic instantiations are blowing up codegen?
  • cargo-bloat: which symbols dominate the binary?
  • tokio-console: which tasks/locks are starving?
  • pprof-rs + criterion: capture flamegraphs directly from benchmarks.

24.3 Mechanical Detail-Hardening Pass

By now, every previous module has fed the hardening/ workspace. Roll it up into one final release-checklist.md: - [ ] lto = "fat", codegen-units = 1, panic = "abort", strip = "symbols" in release. - [ ] PGO instrumented build → representative workload → optimized build (see Appendix A). - [ ] BOLT post-link optimization on the final binary if available. - [ ] Cross-compilation matrix green for all target triples. - [ ] cargo deny, cargo audit, cargo geiger, cargo semver-checks all green. - [ ] Miri clean on unsafe code paths. - [ ] Loom clean on concurrent paths. - [ ] Property tests, fuzz harness, snapshot tests in CI. - [ ] tracing + metrics + traces wired and tested. - [ ] Runbook, ADRs, threat model, and SECURITY.md present. - [ ] Reproducible build verified via cargo vet or a SOURCE_DATE_EPOCH-pinned Docker image.

24.4 Lab-"Defend the Design"

Schedule a 45-minute mock review with a senior peer (or record yourself if none is available). Present: - The architecture diagram. - One slide per non-obvious decision (e.g., "why sharded RwLock instead of dashmap", "why tokio over glommio"). - A live demo of the test suite. - A live demo of one production-hardening tool (PGO, BOLT, or fuzz corpus).

The deliverable is the defense, not the slides. If you cannot answer "what fails first under load?" or "what is your worst-case allocation pattern?", you have not yet finished the curriculum.

24.5 Idiomatic & Clippy Drill

  • Final pass: cargo clippy --workspace --all-targets --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W clippy::cargo. Fix or #[allow] with a rationale comment for each remaining lint. Zero unjustified allows.

24.6 Production Hardening Slice

  • Tag the capstone repo v1.0.0. Generate a release artifact with cargo dist. Sign the release with cosign or a Sigstore-compatible flow. The final commit hash is the artifact you reference on your resume.

Month 6 Deliverable

The chosen capstone (see CAPSTONE_PROJECTS.md): - Compiler track: a merged or in-review PR against rust-lang/rust. - Fintech track: a benchmarked matching engine in a public repo. - Kernel track: a rust-for-linux driver with KUnit tests.

Plus the hardening/ workspace, now a publishable Cargo template under your name.

You are done. The next steps are no longer pedagogical; they are professional.

Appendix A-Production Hardening Reference

This appendix consolidates the hardening slices distributed throughout the curriculum. By week 24 the reader's hardening/ workspace should contain working examples of every section below.


A.1 Compiler-Driven Optimization

  • What it is: LLVM optimizes across crate boundaries at link time, enabling cross-crate inlining, dead-code elimination on monomorphized paths, and devirtualization.
  • Modes:
  • lto = false (default debug): per-codegen-unit only.
  • lto = "thin": ThinLTO. Parallel, fast, ~95% of fat-LTO's wins. Default for new release builds.
  • lto = "fat": full LTO. Single-threaded, slow, but produces the smallest/fastest binaries. Use for shipped binaries.
  • lto = "off": explicit opt-out (rare).
  • Combine with codegen-units = 1 for maximum cross-function optimization. The cost is parallelism: a single codegen unit cannot be compiled in parallel.
  • Cargo profile snippet:
    [profile.release]
    lto = "fat"
    codegen-units = 1
    panic = "abort"
    strip = "symbols"
    

A.1.2 Profile-Guided Optimization (PGO)

PGO uses runtime profiles to guide layout, inlining, and branch-prediction hints.

Workflow: 1. Instrument:

RUSTFLAGS="-Cprofile-generate=/tmp/pgo" \
  cargo build --release --target=x86_64-unknown-linux-gnu
2. Run a representative workload with the instrumented binary. The "representative" qualifier is the entire challenge of PGO; a synthetic benchmark gives you a binary tuned for synthetic benchmarks. 3. Merge profiles:
llvm-profdata merge -o /tmp/pgo/merged.profdata /tmp/pgo
4. Re-build with the profile:
RUSTFLAGS="-Cprofile-use=/tmp/pgo/merged.profdata" \
  cargo build --release --target=x86_64-unknown-linux-gnu

Expect ~5–15% throughput wins on hot paths. Do not enable PGO on first-shipping binaries; it adds build complexity for a marginal win. Enable once the workload is well-characterized.

A.1.3 BOLT (Binary Optimization and Layout Tool)

  • BOLT is a post-link optimizer that rewrites the binary based on perf record data. Stacks on top of PGO.
  • Workflow: build with - Wl,-qto keep relocations, runperf recordon a representative workload, runllvm-bolt` with the perf data.
  • Used in production by Meta and the rustc team itself (rustc's own binary is BOLTed).

A.1.4 target-cpu=native and feature gating

  • RUSTFLAGS="-C target-cpu=native" enables every ISA extension the build host supports-not portable. Use only for self-hosted services where the deployment hardware is known.
  • For portable binaries with runtime feature dispatch: is_x86_feature_detected!("avx2") plus multiversion macro for compile-time fan-out. Pattern from simd-json, rav1e.

A.2 Cross-Compilation

A.2.1 Targets to know

Triple Use case
x86_64-unknown-linux-musl Statically linked Linux server binaries; no glibc dependency.
aarch64-unknown-linux-gnu Graviton, Ampere, Apple Silicon Linux VMs.
aarch64-apple-darwin Apple Silicon native.
x86_64-pc-windows-msvc Windows native (preferred over gnu).
wasm32-unknown-unknown Browser/runtime-less WASM.
wasm32-wasip2 Server-side WASM with WASI.
thumbv7em-none-eabihf Cortex-M4F (no_std, embedded).
riscv64gc-unknown-linux-gnu RISC-V Linux.

A.2.2 Toolchain mechanics

  • rustup target add <triple> adds prebuilt std for the target.
  • For native deps (C libraries), use cross (cross build --target=...) which uses Docker images with the right toolchains pre-installed.
  • For pure-Rust crates, cargo zigbuild (using Zig as the C linker) is a popular zero-config alternative.

A.2.3 Static linking

  • musl + - C target-feature=+crt-static` produces a fully static binary. Ideal for distroless containers.
  • Beware: musl's allocator is slow; for performance-critical static binaries, use mimalloc or jemalloc as the global allocator.

A.3 Supply-Chain & Soundness Auditing

A.3.1 cargo-deny

  • License allowlist ([licenses]).
  • Banned crates ([bans])-used in Month 5 to enforce hexagonal layering.
  • Advisory database integration ([advisories]) for known CVEs.
  • Source allowlist ([sources])-only crates.io and your private registry.

A.3.2 cargo-audit

  • RustSec advisory database. Run on every CI build.
  • cargo audit fix for trivial yanks; for non-trivial CVEs, document the response in SECURITY.md.

A.3.3 cargo-geiger

  • Counts unsafe usage in your dependency tree. Outputs a "radioactivity score."
  • Use as a signal, not a gate: unsafe per se is not bad. A high score in a leaf crate (e.g., a serializer) is normal; a high score in a domain crate is a smell.
  • Track the score over time; sudden increases indicate a dependency added unsafe code in a minor bump.

A.3.4 cargo-vet

  • Mozilla's supply-chain audit tool. Lets your team certify specific crate-version pairs as audited; CI fails if an unaudited dep enters the graph.
  • Heavyweight; appropriate for high-assurance teams (browsers, kernels, payment systems).

A.3.5 cargo-semver-checks

  • Semantic-versioning lint. Catches API breakage that would require a major bump.
  • Run on every PR that touches a public crate.

A.3.6 cargo-machete and cargo-udeps

  • Detect unused dependencies. Each unused dep is a supply-chain liability.

A.4 Soundness Validation

A.4.1 Miri

  • Interpreter for MIR. Detects undefined behavior: dangling pointers, OOB access, type confusion, data races (with - Zmiri-many-seeds`).
  • cargo +nightly miri test. ~50–100× slower than native. Used in CI as a separate job.
  • Strict-provenance mode catches a class of bugs the default mode misses: - Zmiri-strict-provenance`.

A.4.2 Loom

  • Permutation-checker for concurrent code. Replace std::sync::* and std::thread::* with loom::sync::* and loom::thread::* under a feature gate; loom::model(|| ...) exhausts interleavings.
  • Used by tokio, crossbeam, parking_lot. Use it for any data structure with hand-written atomics.

A.4.3 ThreadSanitizer / AddressSanitizer / MemorySanitizer / LeakSanitizer

  • Nightly-only via - Z sanitizer=thread|address|memory|leak. Requires - Z build-std.
  • Catches dynamic UB at runtime. Stack on top of fuzzing for maximum coverage.

A.4.4 KANI

  • Bounded model checker for Rust (formal verification). Annotate functions with #[kani::proof], write harnesses, run cargo kani.
  • Ideal for cryptographic primitives, parsers, and unsafe invariants.

A.5 Reproducibility and Distribution

A.5.1 Reproducible builds

  • Pin rust-toolchain.toml.
  • Set SOURCE_DATE_EPOCH and avoid build.rs non-determinism (no system-time stamps, no environment leaks).
  • Build inside a deterministic container image (Nix, Bazel, or a pinned Docker tag with content hash).

A.5.2 cargo-dist

  • Generates GitHub Actions to build cross-platform release artifacts and installers (shell installer, Homebrew formula, MSI).
  • The recommended baseline distribution path for a CLI or daemon.

A.5.3 Signing and SBOM

  • cosign for artifact signatures (Sigstore).
  • cargo cyclonedx or cargo sbom for CycloneDX/SPDX SBOM generation.
  • Both are now compliance prerequisites in regulated environments.

A.6 The Hardening Workspace Structure

By week 24, the hardening/ workspace should contain:

hardening/
  .cargo/config.toml          # global RUSTFLAGS, target dirs
  rust-toolchain.toml         # pinned channel
  deny.toml                   # cargo-deny rules
  cargo-vet/                  # vet store
  ci/
    lint.yml                  # fmt + clippy
    test.yml                  # test + miri + loom + sanitizers
    cross.yml                 # cross-compilation matrix
    audit.yml                 # cargo audit + deny + geiger
    fuzz.yml                  # scheduled continuous fuzzing
    pgo.yml                   # PGO build pipeline
  scripts/
    pgo.sh                    # instrument-run-rebuild script
    bolt.sh                   # post-link BOLT pass
    bench-baseline.sh         # criterion baseline comparison
  RELEASE_CHECKLIST.md
  SECURITY.md
  THREAT_MODEL.md

This is the artifact that should accompany every Rust project you ship after week 24. It is the "boring" half of professional Rust.

Appendix B-Build-From-Scratch Data Structures Reference

A working Rust engineer should have implemented each of the following at least once, with property tests, Miri, and (where concurrent) Loom. This appendix sketches the minimal-viable design for each. Full implementations are the labs in Months 3, 4, and 6.


B.1 Single-Threaded Hash Map (Open Addressing, Robin Hood)

When: pedagogical only-hashbrown::HashMap is faster than anything you will write.

Design: - Single Vec<Entry> backing array; entries store (hash, key, value) or a tombstone. - Robin Hood probing: track each entry's distance from its ideal slot; on insert, swap with any entry whose distance is shorter than the inserter's. This bounds variance in probe length. - Resize at load factor ~0.875. - hashbrown (used by std since 1.36) further uses SIMD for parallel probe; that is the next exercise after this one.

Lab outcomes: cache-friendly layout intuition, the cost of generic Hasher, niche optimization in Option<u64> for entry-tag storage.


B.2 B-Tree Map

When: ordered iteration, range queries, on-disk indexes (with adapted node sizes).

Design: - Branching factor B (std uses 6; for cache-line-fit nodes you might use 11–15 depending on key/value sizes). - Two node kinds: leaf and internal. Implement as enum or as separately-allocated structs with a tag. - MaybeUninit<K; 2*B-1> for keys; insertions copy-shift, splits move half to a new node. - Walking the tree mutably is the unsafe-rust master class-see std's BTreeMap source for the canonical handling of NodeRef<BorrowType, K, V, NodeType>.

Lab outcomes: complex unsafe with strong invariants, MaybeUninit for arrays, PhantomData for borrow-type encoding.


B.3 Sharded Concurrent Hash Map

When: production reads/writes, when contention is moderate. The pragmatic choice for almost every concurrent map need.

Design: - N shards, each RwLock<HashMap<K, V>>. N typically 16–64; tune via cargo bench for your contention pattern. - Hash key, shard_idx = hash % N, lock the shard, perform the op. - Iteration is delicate: lock shards in order, snapshot or hold the lock for each.

Lab outcomes: the RwLock vs Mutex tradeoff, the cost of Hash + Eq re-evaluation, the dashmap API study.


B.4 Lock-Free Hash Map (Open-Addressed, Epoch-Reclaimed)

When: extreme contention, when flurry or dashmap are not enough.

Design: - Slots are AtomicPtr<Entry> (or tagged atomic word for inline values). - Insert: probe via CAS, inserting Entry. Failures retry to next slot. - Delete: tombstone via CAS. - Resize: allocate new table, transfer entries via help_resize cooperative pattern. Old table reclaimed via crossbeam-epoch once no thread observes it. - Memory ordering: Acquire/Release on slot CAS; SeqCst only when establishing a total order on resize commits.

Lab outcomes: every paragraph above is its own bug class. This is the hardest data structure in the curriculum and the one that most differentiates expert from journeyman Rust engineers.


B.5 SPSC Lock-Free Ring Buffer

When: audio threads, real-time control loops, log producers.

Design: - Fixed-capacity [UnsafeCell<MaybeUninit<T>>; CAP] (CAP a power of two for cheap modulo). - head: AtomicUsize, tail: AtomicUsize, each on its own cache line (CachePadded). - Producer: load tail (Relaxed), check space against head (Acquire), write slot, store tail (Release). - Consumer: symmetric, swapping head/tail. - Wait-free per side; needs no CAS, only atomic loads/stores.

Lab outcomes: cache-line awareness, Acquire/Release pairing, the pattern for "check then act" without locks.


B.6 MPMC Bounded Queue (`crossbeam::ArrayQueue - style)

When: work-stealing schedulers, bounded work pools.

Design: - Slot array with AtomicUsize "stamp" per slot encoding (lap, index, occupied flag). - Producer CAS the stamp from "empty at lap N" to "occupied at lap N". - Consumer CAS from "occupied at lap N" to "empty at lap N+1". - The stamp scheme avoids the ABA problem and the need for hazard pointers.

Lab outcomes: encoding state in atomics, lock-free without epoch reclamation, why Vec::reserve is the right initialization shape.


B.7 Intrusive Doubly-Linked List

When: kernel code, async runtimes' waker lists, anything that must avoid per-node allocation.

Design: - The list does not own nodes. Nodes own their Pointers { prev: Option<NonNull<Node>>, next: Option<NonNull<Node>> }. - Nodes must be Pinned because the list stores raw pointers to them. - unsafe API: the consumer asserts that nodes outlive the list and are not aliased. This is the pattern in tokio::sync::Notify, parking_lot::WaitQueue, and intrusive-collections.

Lab outcomes: Pin in anger, the safety contract for intrusive structures, why LinkedList in std is rarely the right answer.


B.8 Slab Allocator

When: lots of small fixed-size allocations with churn (e.g., per-connection state in a server).

Design: - A Vec<Slot<T>> where Slot<T> = { value: MaybeUninit<T>, next_free: usize }. - A free-list head index. Insert: pop free-list, write value, return index. Remove: push index onto free-list, drop value. - O(1) insert and remove, integer-handle indexing, no per-element allocation.

Lab outcomes: slab crate study, generational indices for ABA defense (see generational-arena), the relationship to ECS storage patterns in game engines.


B.9 Bump Allocator (Arena)

When: short-lived per-request allocations, parser ASTs, anything where the lifetime ends together.

Design: - A Vec<u8> (or chunked list of Vec<u8>s for unbounded growth) and a current offset. - Allocate: align current, advance, return pointer. - Drop everything at once (no per-item destructors run by default; see bumpalo::Bump::alloc vs alloc_with).

Lab outcomes: alignment math, pointer arithmetic with provenance, why Rust ASTs and rustc itself use bump allocation.


B.10 Skip List (Concurrent)

When: ordered concurrent containers; the foundation of crossbeam-skiplist. Used in RocksDB-style memtables.

Design: - Tower of forward pointers per node, height geometrically distributed. - Concurrent insert: build the new node bottom-up, link levels via CAS. Stale links retry. - Removal: logical (mark deleted) then physical (unlink).

Lab outcomes: lock-free with non-trivial structure, randomization in algorithm design, the alternative to balanced trees in concurrent settings.


B.11 LSM-Tree Memtable + SSTable Pair

When: storage engines (RocksDB, LevelDB, Sled-style).

Design: - Memtable: a sorted concurrent map (skip list or sharded BTree) holding recent writes. - WAL (write-ahead log) appended for durability. - On size threshold, flush memtable to an immutable SSTable on disk: sorted key-value pairs with a sparse index and Bloom filter. - Compaction merges SSTables periodically.

Lab outcomes: durability/throughput tradeoffs, memory-mapped vs read-syscall, Bloom filters in practice. Optional capstone-tier.


Difficulty Ranking

Tier Structures
Warmup Hash map (single-threaded), Slab, Bump
Intermediate B-Tree, Sharded concurrent map, SPSC ring
Advanced MPMC queue, Intrusive list
Expert Lock-free hash map, Skip list, LSM-tree

Pick at least one from each tier. Ship with property tests, benchmarks, and Miri.

Appendix C-Contributing to rustc: A Playbook

Most engineers never contribute to a compiler. The barrier is reputational ("compilers are hard"), not technical. This appendix is the on-ramp.


C.1 Mental Model

rustc is a query-driven, incrementally-compiled, multi-stage compiler whose front-end (HIR), middle (MIR), and back-end (LLVM/Cranelift codegen) are loosely coupled by a query system. You do not need to understand all of it to land a useful PR. You need to understand:

  1. The query system at a high level (queries memoize derived data; recompiles invalidate dependent queries).
  2. The MIR if you are touching borrow check, optimizations, or const-eval.
  3. The HIR and resolver if you are touching name resolution or module structure.
  4. The lint infrastructure if you are adding a lint (the easiest first PR).
  5. The diagnostic infrastructure if you are improving an error message (the easiest possible first PR).

C.2 The Pipeline, in 30 Seconds

Source
  │ Lexer ── tokens
  │ Parser ── AST
  │ Macro expansion + name resolution ── expanded AST
  │ AST → HIR lowering
  │ Type checking + trait solving (on HIR)
  │ HIR → THIR → MIR
  │ Borrow checking (on MIR)
  │ MIR optimization (inlining, const-prop, dead-code, dataflow analyses)
  │ MIR → LLVM IR (or Cranelift IR)
  │ LLVM optimization + codegen
Object code → linker → binary

Each arrow above is a query. Each query has inputs and outputs. The tcx: TyCtxt<'tcx> is the god-object that holds the query context.


C.3 Setting Up

git clone https://github.com/rust-lang/rust
cd rust
./x.py setup           # choose 'compiler' or 'library' profile
./x.py check           # ~5 min on a fast machine; sanity check
./x.py build library/std --stage 1

Edit, then:

./x.py build --stage 1 compiler/rustc
./build/host/stage1/bin/rustc --version

For most PRs, stage 1 is enough. Only stage 2 fully self-bootstraps; reserve it for final verification and ./x.py test.

Use rust-analyzer with the rustc workspace; the maintainers ship a config (.vscode/settings.json template in the repo root).


C.4 Where the Easy Wins Are

In rough order of difficulty, the on-ramp issues:

C.4.1 Diagnostic improvements

  • An error like error[E0308]: mismatched types whose note: line could be more helpful. Search the issue tracker for A-diagnostics + E-easy.
  • Touch compiler/rustc_*/messages.ftl (Fluent translations) and the corresponding *.rs site of emission. Often <30 lines.

C.4.2 New clippy lints

  • The clippy repo (rust-lang/rust-clippy) maintains a good-first-issue queue. Each lint is roughly: declare in clippy_lints/src/lib.rs, write an EarlyLintPass or LateLintPass impl, add UI tests, document.
  • Lower bar than rustc proper for review.

C.4.3 Small library PRs (library/std, library/core, library/alloc)

  • API additions need an ACP (API Change Proposal) before code; small soundness fixes do not.
  • Library team ships fast and reviews kindly; great track for a first PR.

C.4.4 Diagnostic suggestion / rustfix integration

  • Existing errors that lack - -helpsuggestions. Adding a structured suggestion thatcargo fix` can apply is high-impact and well-bounded.

C.4.5 rustdoc improvements

  • Search ranking, intra-doc-link resolution, theme tweaks. Self-contained crate (src/librustdoc/).

C.4.6 MIR optimization passes

  • compiler/rustc_mir_transform/. Each pass is a struct implementing MirPass. Adding a new pass is medium-difficulty; fixing an existing pass is easier and more common.
  • This is where the curriculum lands you by week 23. Examples of tractable PRs: tightening an existing const-prop, adding a new dataflow analysis for a specific shape.

C.4.7 Don't start here (yet)

  • Trait solver (chalkification is in flight; touching it requires deep context).
  • Borrow checker proper (Polonius is similarly in flight).
  • Codegen / LLVM interface.
  • Bootstrap.

C.5 The First-PR Workflow

  1. Pick an issue. Comment @rustbot claim to assign yourself.
  2. Write a one-paragraph plan in the issue or in a draft PR description: what you'll change, where, and how you'll test.
  3. Ask early. The t-compiler/help Zulip stream and the issue's mentor (named in the E-mentor label) want to be asked. Pre-PR clarification beats a rejected PR every time.
  4. Implement, test, format:
  5. ./x.py test compiler/rustc_<crate> --stage 1
  6. ./x.py test src/test/ui --stage 1 if you've changed UI tests (most diagnostics PRs).
  7. ./x.py fmt
  8. Open the PR referencing the issue. Add the r? reviewer suggested by the issue or r?@rustbot for triage.
  9. Address review-expect 1–3 rounds. The reviewers are unpaid volunteers; be patient and concrete.
  10. @bors r=<reviewer> approves; bors will batch and merge. Your name is in the changelog.

C.6 The Compiler Reading Map

When the dev guide is dense, these source files are the most yield-per-page reads:

File What it teaches
compiler/rustc_middle/src/ty/context.rs The TyCtxt: how everything is queried.
compiler/rustc_middle/src/mir/mod.rs MIR: Body, BasicBlock, Statement, Terminator.
compiler/rustc_borrowck/src/lib.rs The borrow checker entry point.
compiler/rustc_mir_transform/src/lib.rs MIR passes: how they're declared and ordered.
compiler/rustc_lint_defs/src/builtin.rs How lints are declared.
compiler/rustc_resolve/src/lib.rs Name resolution.
compiler/rustc_hir/src/hir.rs The HIR data model.
compiler/rustc_codegen_ssa/src/base.rs The codegen façade above LLVM.

Read in this order: MIR → borrow check → MIR transform → lints → resolver → HIR → codegen.


C.7 Adjacent Targets if rustc Is Too Heavy

  • rust-clippy-same machinery, smaller surface, faster review.
  • rust-analyzer-entirely different codebase but high-impact contributions.
  • cargo-large but well-modularized; build-system contributions.
  • rustup-small, contained.
  • miri-interpretive twin of rustc; PRs here teach you the abstract machine.
  • stdlib-library/std, library/core. Often the easiest first merge.

A merged PR in any of these is a credible signal in interviews and grant applications.


C.8 Calibration

A reasonable goal for a curriculum graduate:

  • By end of week 23: a PR open against rust-clippy (a new lint or a false-positive fix) or rust-lang/rust (a diagnostic improvement).
  • By end of capstone: that PR merged.
  • 6 months post-curriculum: a non-trivial PR-a new MIR pass, a stabilized API, a soundness fix.

These are realistic timelines. The maintainers prioritize correctness over speed; do not be discouraged by a 4-week review cycle.

Capstone Projects-Three Tracks, One Choice

The Month 6 capstone is the deliverable that converts this curriculum from study into evidence. Pick one track. The work performed here is the work you describe in interviews and link from a portfolio.


Track 1-Compiler / Tooling

Outcome: a merged PR (or one in advanced review) against rust-lang/rust, rust-clippy, rust-analyzer, or cargo.

Suggested scopes (ranked by tractability)

  1. Diagnostic improvement in rustc. Pick an A-diagnostics issue with a clear reproduction. Improve the error: more accurate span, structured suggestion, better wording. Realistic effort: 20–40 hours including bootstrap, review iterations, UI test churn.
  2. New clippy lint. The clippy issue tracker maintains a queue of "lint requests." Pick one tagged good-first-issue. Implement, test (UI tests + dogfood the lint against the rustc tree), document. Realistic effort: 30–60 hours.
  3. A new MIR optimization pass (advanced). Choose a narrow, well-bounded transform-e.g., a peephole simplification of a specific MIR pattern. Profile its impact with rustc-perf. Realistic effort: 60–120 hours and substantial reviewer hand-holding; treat as stretch.
  4. rust-analyzer feature. Implement a code action or completion improvement. RA's architecture is extremely well-documented; the PR loop is fast.

Acceptance criteria

  • A PR exists, is linked from your portfolio, and has at least one round of review feedback addressed.
  • A short write-up (CAPSTONE_NOTES.md) documenting: what you changed, why, what you learned about the compiler internals, and what reviewers pushed back on.
  • Your local fork has a working stage-1 build of rustc with your patch applied.

Skills exercised

  • Months 4 (macros / unsafe), 6.23 (compiler internals).
  • The hardening discipline matters less here; the deliverable is upstream code, not a service.

Track 2-High-Performance Fintech: Limit-Order-Book Matching Engine

Outcome: a benchmarked, fuzz-tested matching engine for limit and market orders across multiple symbols, single-process, with sub-microsecond p99 hot-path latency on commodity x86_64.

Functional spec

  • Order types: limit (GTC, IOC, FOK), market, cancel, modify.
  • Matching policy: price-time priority. Partial fills allowed. Self-trade prevention configurable.
  • Multi-symbol: a Engine owns N independent symbol books; symbols may be sharded across worker threads.
  • Wire format: a binary protocol (your design or a subset of FIX/SBE).
  • Output: an event stream (Filled, PartiallyFilled, Cancelled, Rejected, BookUpdate) consumed by downstream feed handlers.

Non-functional spec

  • Latency: p50 < 200 ns, p99 < 1 µs for the hot path (order in → match → event out), measured under sustained 1 M orders/sec.
  • Throughput: ≥ 1 M orders/sec sustained on a single symbol on a single core.
  • Determinism: identical inputs produce identical event sequences. No HashMap iteration order in the hot path; use deterministic structures.
  • Fault tolerance: panic-safe (panic = "abort" is acceptable; document operational implications). Persistent log for replay.

Architecture sketch

  • Hot path is single-threaded per symbol. SPSC ring buffer on input, SPSC on output. Cross-thread coordination only at session boundaries.
  • Order book: paired sorted structures (often BTreeMap<Price, OrderQueue> for asks, mirror for bids). For ultimate latency: array-of-price-levels with sparse bitmap; this is the rabbit hole `Aeron - style designs go down.
  • Allocator: mimalloc global, plus per-symbol bump arenas for short-lived order metadata.
  • Memory layout: #[repr(C)] orders, padded to cache-line boundaries; pre-allocated slabs.
  • No async on the hot path. Async is for session/admin paths only. Mixing the two is the most common architectural mistake in this space.

Test rigor

  • Property tests: proptest invariants on the book-total quantity preserved across fills, no cross of bid > ask except during a match, time priority preserved at a price level.
  • Fuzz: cargo-fuzz with `arbitrary - derived order generators. The corpus must include high-volume sequences with cancels and modifies.
  • Loom: any cross-thread sync (admin → engine, engine → publisher) must be Loom-verified.
  • Bench: criterion with regression detection in CI; flamegraphs committed; perf stat outputs (cycles, instructions, IPC, cache-misses) tracked over time.

Hardening pass

  • LTO fat, codegen-units 1, panic abort, target-cpu=native (for the deployed-on-known-hardware case).
  • PGO with a representative replay workload.
  • BOLT post-link.
  • Deterministic build via Docker pinned to a content hash.

Acceptance criteria

  • Public repo with the above.
  • A README that includes a flamegraph, a perf stat table, and a latency CDF.
  • A THREAT_MODEL.md covering the inputs you do and do not validate.
  • An interview-defensible answer to: "What does your worst-case allocation pattern look like under a 100× burst?"

Skills exercised

  • Months 3 (concurrency), 4 (unsafe / FFI for the wire codec), 5 (production architecture though the hot path skips most of the hexagonal), 6.21 (custom data structures), 6.22 (allocators).

Track 3-Kernel: A rust-for-linux Character Device

Outcome: a working out-of-tree Rust kernel module implementing a non-trivial character device, with KUnit tests, building cleanly against a recent mainline kernel.

Functional spec

  • A character device (/dev/<yourname>) that exposes an in-kernel ring buffer.
  • Operations: read (drains the ring), write (appends), ioctl for resize/clear/stats, mmap for zero-copy access (stretch).
  • Multi-reader / multi-writer with appropriate kernel synchronization (SpinLock, Mutex from the kernel crate, not std).
  • Sysfs entries for runtime tuning.

Why this scope

  • Touches every cross-FFI surface: char device registration, file operations, copy_from_user/copy_to_user, sysfs, locking.
  • Forces you to read kernel-side Rust idioms (Box::try_new, fallible alloc, Pin<&mut Self> everywhere, Arc-equivalents).
  • The rust-for-linux toolchain itself is a learning surface: pinned rustc, custom libcore subset, no std.

Build environment

  • Linux ≥ 6.8 (Rust support is stable enough for out-of-tree work).
  • rustup toolchain link kernel <path> to point at the kernel-supported rustc.
  • A local kernel build with CONFIG_RUST=y, CONFIG_SAMPLES_RUST=y.

Test rigor

  • KUnit-based unit tests inside the module.
  • `selftest - style scripts running the device through real read/write/ioctl from userspace.
  • Stress test: N concurrent readers and writers with taskset pinning, watch for KASAN/KCSAN reports.

Hardening pass

  • Kernel-side: KASAN (kernel address sanitizer), KCSAN (concurrency sanitizer), lockdep enabled in your test kernel.
  • Module-side: every unsafe block carries a // SAFETY: comment justifying the kernel invariants.
  • A `dmesg - clean run on insertion, exercise, and removal.

Acceptance criteria

  • The module builds, loads, exercises end-to-end, unloads, with no KASAN/KCSAN/lockdep warnings.
  • A PR-ready patch series formatted for git format-patch (even if not submitted upstream).
  • A KERNEL_NOTES.md describing the locking model, the failure modes you considered, and the explicit reason you chose SpinLock vs Mutex at each site.

Skills exercised

  • Months 4 (unsafe + FFI to the kernel C API), 6.22 (no_std), 6.23 (compiler internals indirectly via the pinned toolchain).

Cross-Track Requirements

Regardless of track:

  • Hardening workspace integrated. The hardening/ template from Appendix A applies.
  • Architectural Decision Records (ADRs). At least three for the capstone, each ~1 page.
  • Threat model. One page minimum, no matter the track.
  • Defense readiness. You should be able to walk a reviewer through the code in 45 minutes and answer "what fails first under load / fuzzing / a malicious input / a pathological kernel state?"

The track choice signals career direction: compiler track for tooling/PL roles, fintech for HFT/exchange/crypto roles, kernel for OS/embedded/security roles. Do not pick based on what looks easiest; pick based on where you want the next interview loop.