Skip to content

Rust roadmap

Ordered runs of problems, and nothing else: no reading, no progress bar, just the sequence. For a syllabus that interleaves written explainers and can be worked through with friends, see tracks.

Rust: zero to hero

Every rust track in course order, grouped into the syllabus's ten build phases. Ownership is split across three separate tracks on purpose, threads come before async because you build the executor, and unsafe comes last.

342 problems

1 One file, one function, one gate Easy 2 How to read a rustc error message Easy 3 Naming conventions and the style gate Easy 4 let, immutability by default, and mut Easy 5 Shadowing: rebinding is not mutation Easy 6 Integer types: widths, signedness and usize Easy 7 Numeric literals: suffixes, underscores, other bases Easy 8 Integer overflow: debug panics, release wraps Medium 9 checked, wrapping, saturating, overflowing Medium 10 Integer division and remainder: the signs will surprise you Medium 11 Floating point: f32, f64, NaN, and why == is a trap Medium 12 bool and char Easy 13 Casting with as: truncation, sign reinterpretation, saturation Medium 14 From, Into, TryFrom: conversions that cannot lie Medium 15 Statements vs expressions, and the semicolon that changes the type Easy 16 Functions: parameters, return types, early return Easy 17 if is an expression Easy 18 loop, while, for, and break with a value Easy 19 Loop labels and labeled blocks Medium 20 Ranges: .., ..=, and range patterns Easy 21 Tuples and destructuring Easy 22 Arrays: the length is part of the type Easy 23 Vec<T>: the growable one Easy 24 Slices &[T]: the shape that unifies arrays and Vec Medium 25 String vs &str, part 2: functions that take and return text Medium 26 Strings are UTF-8: bytes, chars, and the indexing panic Hard 27 The three rules of ownership Easy 28 Stack, heap, and what a variable actually holds Easy 29 Move semantics: the relay Easy 30 Reading E0382 line by line Easy 31 Copy: the types that don't move Easy 32 Clone: explicit, deep, and never free Easy 33 Ownership through function calls Easy 34 Giving ownership back Medium 35 Partial moves: taking one field and leaving the rest Medium 36 E0509: a Drop type is an atom Hard 37 drop(x) is just a function Easy 38 Drop order is specified Medium 39 Moving out of &mut: take, replace, swap Medium 40 Conditional moves and drop flags Hard 41 Ownership inside loops Medium 42 Shadowing is not moving Easy 43 Method receivers and the consuming builder Medium 44 Leaking is safe Hard 45 Move or borrow: the two ways to pass a value Easy 46 &mut: exclusive access, and why `mut` appears twice Easy 47 How to read a borrow-check diagnostic Easy 48 Non-lexical lifetimes: a borrow ends at its last use Easy 49 E0502: cannot borrow as mutable because also borrowed as immutable Easy 50 E0499: cannot borrow as mutable more than once Medium 51 The owner is frozen too: E0503, E0505, E0506 Medium 52 Slices are borrows Easy 53 &str not &String, &[T] not &Vec<T> — deref coercion and ptr_arg Easy 54 ref, ref mut, and default binding modes in edition 2024 Medium 55 Iterator invalidation, and the bug class that stopped existing Medium 56 Indexing in a loop is usually a borrow-checker workaround — and clippy knows Medium 57 iter_mut(): mutating every element without ever holding two &mut Medium 58 Split borrows: the checker reasons about places, not variables Medium 59 split_at_mut and the disjointness-proof APIs Medium 60 Reborrowing: why &mut sometimes moves and sometimes doesn't Hard 61 Two-phase borrows: why v.push(v.len()) compiles Hard 62 What a &mut parameter actually promises Medium 63 NLL Problem Case #3, the entry API, and the limits of today's checker Hard 64 Closures hold their borrows Medium 65 Interior mutability: moving the check from compile time to run time Medium 66 Structs: naming your data Easy 67 Field init shorthand and struct update syntax Easy 68 Tuple structs and unit structs: the other two shapes Easy 69 impl blocks: associated functions, methods, Self and associated constants Easy 70 Deriving traits, and when derive gets the bounds wrong Easy 71 Enums are real sum types Easy 72 match and exhaustiveness: the compiler as a checklist Easy 73 The wildcard trap: `_` arms freeze your enum against the future Medium 74 Literal, range and or-patterns Easy 75 Binding with @, and what or-patterns demand of bindings Medium 76 Destructuring structs and nested patterns Medium 77 Slice and array patterns, and rest @ .. Medium 78 Match guards, and why they don't count toward exhaustiveness Medium 79 Refutability, and every place a pattern can appear Medium 80 if let, while let, and let ... else Easy 81 Let-chains: the edition-2024 feature that deletes your nesting Medium 82 matches! and the lints that push you toward it Easy 83 #[derive(Debug)] and the three shapes of debug output Easy 84 PartialEq and Eq: why floats can be compared but not Eq Medium 85 PartialOrd and Ord: derived ordering follows declaration order Medium 86 Hash and the Hash/Eq contract Medium 87 Default, #[default] on an enum variant, and ..Default::default() Easy 88 Discriminants, #[repr(u8)], as casts and mem::discriminant Medium 89 pub on structs and enums behaves differently Easy 90 Vec surgery: retain, dedup and extract_if Medium 91 String building: push_str, write!, and the 590x cliff Easy 92 Joining owned strings Easy 93 Case-insensitive search without allocating Easy 94 Zero-copy parsing: borrow, don't allocate Medium 95 HashMap fundamentals: get, get_mut and the Hash/Eq contract Easy 96 The Entry API: one lookup, not two Medium 97 HashMap iteration order is random Medium 98 BTreeMap range queries Medium 99 Set algebra with BTreeSet Easy 100 VecDeque and breadth-first search Easy 101 BinaryHeap is a max-heap, and Reverse is the fix Medium 102 Grouping without itertools: anagram buckets Medium 103 windows and chunks are slice methods, not iterator adapters Medium 104 Cow: borrow until you have to own Medium 105 Cow in API design: impl Into<Cow<str>> Hard 106 In-place mutation vs rebuild, measured Easy 107 Reusing buffers instead of allocating per iteration Medium 108 Everything you can collect into Easy 109 Matching on Option Easy 110 The Option combinator vocabulary Easy 111 Eager vs lazy defaults Easy 112 Borrowing through Option Medium 113 Mutating an Option in place Medium 114 Result, and why it is #[must_use] Easy 115 Result combinators and map_err Easy 116 Converting between Option and Result Medium 117 Propagating with `?` Easy 118 `?` on Option, and bridging to Result Medium 119 Implementing std::error::Error Medium 120 From impls: the machinery behind `?` Medium 121 Error chains and source() Medium 122 Boxed errors and downcasting Medium 123 Adding context without anyhow Hard 124 #[must_use] in depth Medium 125 Collecting an iterator of Results Medium 126 unwrap and expect discipline Medium 127 Documenting failure: # Errors and # Panics Medium 128 Traits as shared behaviour Easy 129 Generic functions and monomorphisation Easy 130 Trait bounds and why f64 is not Ord Easy 131 where clauses and the bound you cannot write inline Easy 132 A generic Stack, and conditional impl blocks Medium 133 Display, Debug, and the ToString blanket impl Easy 134 Supertraits and default methods Medium 135 From, Into, and your first blanket impl with teeth Medium 136 TryFrom, TryInto, and the type that cannot exist Medium 137 AsRef, Borrow, and signatures your callers can actually use Medium 138 Deref and the coercion you have been using all along Medium 139 Deref polymorphism, and the day your method disappears Medium 140 Operator overloading: + is just a trait method Medium 141 Associated type or generic parameter? Hard 142 impl Trait in argument position is not a generic Medium 143 Returning impl Trait, and what edition 2024 changed Hard 144 Blanket impls, and why you can never carve out an exception Hard 145 The orphan rule and the newtype escape Hard 146 Extension traits: adding methods to types you do not own Medium 147 Sealed traits: public to use, closed to implement Medium 148 Borrow it or own it: your first lifetime decision Easy 149 Let elision do it Easy 150 `longest`: writing your first `'a` Medium 151 Two inputs, one output lifetime Medium 152 Zero-copy tokenizer: `Parser<'a>` Hard 153 Make the lint happy: `'_` in return paths Easy 154 `T: 'static` in practice — a type-erased handler registry Hard 155 Generics, traits and lifetimes in one signature Medium 156 Closures 101: build a pipeline of boxed closures Easy 157 Fn, FnMut, FnOnce: the closure hierarchy Medium 158 move: when the closure must own its captures Medium 159 iter, iter_mut, into_iter: all three, one Vec Easy 160 IntoIterator: one bound that accepts five containers Medium 161 The double-reference wall: &&x, copied and cloned Medium 162 Laziness: prove the pipeline interleaves Easy 163 map, filter, filter_map: parse host:port lines Easy 164 fold and try_fold: a total that refuses to overflow Medium 165 take_while and skip_while: split a message at the blank line Easy 166 zip and enumerate: pair two lists and report the leftovers Easy 167 peekable and next_if: write a tokeniser Hard 168 flat_map: expand a range spec like "1-3,7,10-12" Medium 169 scan: a running balance that stops at the floor Medium 170 Implement Iterator by hand: the Collatz sequence Medium 171 from_fn and successors: iterators without writing a struct Easy 172 any, all, find, position: audit a list of records Easy 173 min and max: the tie-breaking rule nobody reads Medium 174 partition and unzip: two collections from one pass Medium 175 collect and the turbofish: three targets, one source Medium 176 size_hint and DoubleEndedIterator: an iterator with two ends Hard 177 Write your own adapters, with an extension trait Hard 178 Make your own collection iterable, all three ways Hard 179 Chains versus loops: top-k, and a function that must not allocate Medium 180 The FnMut borrow gauntlet: a log you can read afterwards Medium 181 Modules are a tree you declare, not files you discover Easy 182 Paths: absolute, relative, crate::, self::, super:: Easy 183 Everything is private by default: the two privacy rules Easy 184 Restricted visibility: pub(crate), pub(super), pub(in path) Medium 185 use declarations: nested groups, as, self, and _ Medium 186 Glob imports, shadowing, and ambiguity Medium 187 Three namespaces: types, values, and macros Medium 188 pub use re-exports and the facade pattern Hard 189 The preludes — all five of them Medium 190 macro_rules! scoping is not item scoping Hard 191 Module file layout: foo.rs, foo/mod.rs, and #[path] Medium 192 #[cfg], cfg!, cfg_attr and the new cfg_select! Medium 193 Version requirements: caret, tilde, wildcard, pre-releases Medium 194 Cargo features and the additivity rule Hard 195 SemVer for Rust APIs: what actually breaks Hard 196 Build your own #[test]: catching panics deterministically Hard 197 Test selection: filters, #[ignore], and the libtest CLI Medium 198 The stack, the heap, and `Box<T>` Easy 199 Recursive types need indirection: E0072 Easy 200 `large_enum_variant`: boxing the fat variant Medium 201 `Rc<T>`: shared ownership by reference counting Medium 202 Inside `Rc`: `strong_count`, `ptr_eq`, `get_mut`, `try_unwrap` Medium 203 `Rc::make_mut`: copy-on-write shared data Hard 204 `Rc<str>`: interning without double indirection Medium 205 `Cell<T>`: mutation by moving values in and out Medium 206 `RefCell<T>`: borrowing checked at runtime Medium 207 When `RefCell` panics: re-entrancy and borrows held too long Hard 208 `let _ =` versus `let _x =`: the guard that dies instantly Medium 209 RAII: build your own guard Medium 210 `Rc<RefCell<T>>`: the workhorse, and when it is a smell Hard 211 Reference cycles leak memory Hard 212 `Weak<T>`: non-owning references that break cycles Hard 213 `Rc::new_cyclic`: self-reference in one step Hard 214 `OnceCell`: write-once interior mutability Medium 215 `static mut` is dead: what edition 2024 changed Medium 216 Indices as handles: the generational arena Hard 217 Bake-off: the same tree, an arena and an `Rc` graph Hard 218 Drop order in composite structures Medium 219 Build `Cell<T>` and `RefCell<T>` from scratch Hard 220 Box<dyn Trait>: trait objects, DSTs and dynamic dispatch Medium 221 Dyn compatibility: the actual rules Hard 222 Static vs dynamic dispatch, measured Hard 223 Enum dispatch: the third option Medium 224 Dispatch through Rc: Rc<dyn Trait> and shared behaviour Medium 225 Trait objects and lifetimes: Box<dyn Trait + 'a> Hard 226 Designing signatures: T, &T, &mut T, impl Into<T> Medium 227 Where the allocations are: the counting allocator Easy 228 Type sizes, enum layout and niche optimisation Medium 229 repr(Rust) reorders your fields — and that's the point Medium 230 repr(C): computing struct layout by hand Medium 231 The other reprs: transparent, packed, align, integer enums Hard 232 Memory layout and cache behaviour: flat arrays, AoS vs SoA Medium 233 Capacity and pre-sizing, graded Easy 234 Needless clone: the most common review comment in Rust Medium 235 Sorting: sort vs sort_unstable vs sort_by_cached_key Medium 236 Bounds checks: when they cost, and five safe ways to remove them Hard 237 Auto-vectorisation and why floating-point addition blocks it Hard 238 #[inline], #[inline(always)], #[cold] and the codegen-unit trap Hard 239 Custom hashing: beating SipHash on integer keys Hard 240 String and text performance, graded Medium 241 Iterator performance: where the abstraction leaks Medium 242 Const generics: moving a size from runtime to compile time Hard 243 const fn and compile-time evaluation Hard 244 Threads, spawn and join Easy 245 move closures: why the compiler forces your hand Easy 246 JoinHandle, panics and thread::Result Medium 247 Scoped threads: borrow, do not clone Easy 248 Balanced ranges: the arithmetic before the threads Easy 249 Send: what it actually guarantees Medium 250 Sync: T is Sync exactly when &T is Send Medium 251 unsafe impl Send, and the disjoint-capture trap Hard 252 Arc: shared ownership across threads Easy 253 Arc gives you sharing, not mutability Easy 254 Mutex and MutexGuard: you cannot forget to unlock Easy 255 Guard lifetimes: still holding the lock, still not meaning to Hard 256 Poisoning: what a panic does to a lock Medium 257 Index tagging: the pattern that makes concurrency testable Medium 258 Deadlock by lock ordering: the guarantee Rust does not give you Hard 259 mpsc channels: message passing basics Easy 260 The drop(tx) hang: a bug with no diagnostics Medium 261 sync_channel: bounded buffers and backpressure Medium 262 Condvar: a wakeup is a hint, the predicate is the truth Hard 263 Safe parallel mutation with chunks_mut Medium 264 Barrier: keeping workers in lock step Medium 265 park and unpark: building a blocking primitive by hand Hard 266 Atomics: counters, flags and fetch_* Medium 267 CAS loops: compare_exchange and fetch_update Hard 268 A store-buffer memory-model interpreter Hard 269 A single-producer single-consumer ring buffer Hard 270 OnceLock: one initialisation, however many threads race for it Medium 271 static mut and the edition-2024 wall Medium 272 Build a spinlock from AtomicBool and UnsafeCell Hard 273 Rc<RefCell<T>> vs Arc<Mutex<T>>, and the arena that beats both Medium 274 Futures are lazy: nothing happens until you poll Easy 275 The `Future` trait: `Output`, `Poll`, and your first hand-written future Easy 276 `block_on`: the smallest thing that can run a future Easy 277 Countdown futures and the shape of `Pending` Easy 278 Implementing `Wake`: a real task waker and the lost-wakeup bug Medium 279 `RawWaker` and `RawWakerVTable`: building a `Waker` with unsafe Hard 280 `Unpin` in practice: making `Pin<&mut Self>` usable without unsafe Medium 281 Hand-compile an `async fn` into a state machine Hard 282 `poll_fn`: a future from a closure Easy 283 Hand-rolling `join`: running two futures concurrently Medium 284 Hand-rolling `select`: racing futures and dropping the loser Medium 285 Structural pinning and safe pin projection by hand Hard 286 The minimal single-threaded executor Hard 287 Spawning, `JoinHandle`, and getting values back out Hard 288 A logical-time reactor: timers without a clock Hard 289 Fairness and starvation in your scheduler Medium 290 Cancellation is just: stop polling and drop Medium 291 Cancel safety, blocking, and locks across `.await` Hard 292 Async recursion, async closures, and streams Hard 293 The five superpowers — and the four myths Easy 294 `unsafe {}` vs `unsafe fn`: edition 2024 splits the two meanings Easy 295 Safety comments as a discipline: `// SAFETY:` and `# Safety` Easy 296 Raw pointers: `*const T` and `*mut T` Easy 297 `&raw const` / `&raw mut`: a pointer without a reference Medium 298 Pointer arithmetic: `offset`, `add`, `sub`, `wrapping_*` Medium 299 `ptr::read`, `write`, `copy`, `copy_nonoverlapping`, `drop_in_place` Medium 300 `NonNull<T>`, null, and dangling-but-aligned Medium 301 Provenance: a pointer is not an integer Hard 302 Validity invariants vs safety invariants Medium 303 `UnsafeCell`: the only legal path to interior mutability Medium 304 Implementing `Vec`: layout, allocation, push, pop Hard 305 Implementing `Vec` part 2: `insert`, `remove`, `Deref`, `IntoIter` Hard 306 Implementing a reference-counted pointer Hard 307 `PhantomData`, variance and drop-check Hard 308 Leaking is safe, and exception safety Hard 309 Splitting borrows: implement `split_at_mut` Medium 310 `union`, `transmute` and `MaybeUninit` Hard 311 Partial initialisation and leak-free unwinding: a `MaybeUninit` ring buffer Hard 312 Your first macro_rules!: matchers and transcribers Easy 313 Fragment specifiers: the complete tour Medium 314 expr is atomic, tt is not: the precedence trap Medium 315 Repetition: $(...),* and friends Easy 316 Nested repetition and zipping metavariables Hard 317 Counting repetitions without ${count} (which is still unstable) Medium 318 Macro recursion and incremental TT munchers Hard 319 Internal rules and push-down accumulation Hard 320 Callbacks and TT bundling Hard 321 Macro hygiene: why your variable didn't leak Medium 322 Follow-set rules: why the compiler rejects your matcher Hard 323 Building std's macros: vec!, matches!, a HashMap literal Medium 324 A mini DSL: compile-time expression evaluation Hard 325 Calling C from Rust: libc is already linked Medium 326 Edition 2024: unsafe extern blocks and safe declarations Medium 327 Edition 2024: #[unsafe(no_mangle)] and calling Rust from C in one file Hard 328 C strings, ownership across the boundary, and FFI-safe types Medium 329 Nullable pointer optimization, callbacks and qsort Hard 330 Opaque pointers, variadics, and the FFI hazard catalogue Hard 331 Type-driven design: newtypes, smart constructors, builders, typestate Hard 332 Enum state machines with data-carrying transitions Hard 333 Folding an AST: separating traversal from action Hard 334 A macro-generated FFI binding layer Hard 335 Build Rc<T> and RefCell<T> from scratch Hard 336 Capstone: an ownership-only job scheduler Hard 337 Capstone: build a thread pool Hard 338 Capstone: an async executor with a logical-time reactor Hard 339 Capstone: a fully-typed fallible parser Hard 340 Capstone: audit and repair an unsound abstraction Hard 341 Unsound API smells: the patterns that are always wrong Medium 342 Designing safe wrappers around unsafe FFI Hard