An open API service indexing awesome lists of open source software.

https://github.com/utkarsh5026/rust-scratch

πŸ¦€πŸ“š A learn-by-doing Rust workspace β€” pick a concept, get a ladder of 7-9 problems (easy β†’ mastery), and implement each one yourself with staged hints, never handed the solution. Powered by a custom rust-practice coaching skill. πŸš€
https://github.com/utkarsh5026/rust-scratch

async-rust claude-code education exercises learn-by-doing learning lifetimes ownership practice rust rust-lang systems-programming

Last synced: 6 days ago
JSON representation

πŸ¦€πŸ“š A learn-by-doing Rust workspace β€” pick a concept, get a ladder of 7-9 problems (easy β†’ mastery), and implement each one yourself with staged hints, never handed the solution. Powered by a custom rust-practice coaching skill. πŸš€

Awesome Lists containing this project

README

          

# rust-scratch

My personal **learn-by-doing** workspace for mastering Rust. Not a product β€” a
practice gym. I name a concept, get a ladder of problems, and implement each one
myself with coaching (never handed the answer), until I actually understand it.

The whole thing is built around the **`rust-practice` skill** + a structured
mastery curriculum in [`ROADMAP.md`](./ROADMAP.md).

## Where I'm at


Rust mastery dashboard β€” rank, XP, phase progress, achievements, streak

## The core loop

1. Name a concept β†’ `practice ` (or `/rust-practice `)
2. A ladder of **7-9 problems** (easy β†’ mastery) is scaffolded into `src/bin/.rs`
3. Implement one rung at a time; run `cargo run --bin ` to check
4. Get **staged hints** if stuck β€” the solution is revealed only if you ask
5. Each ladder ends in a **build-it-from-scratch capstone**

Every solved rung earns **XP** and feeds a gamified dashboard (`cargo run --bin
stats` or `/stats`): ranks πŸ₯šβ†’πŸ¦€β†’β€¦β†’πŸ‘‘, achievements (Hint-Free, One-Shot,
Miri-Clean, Capstone, Phase Clear), and a daily **streak**.

```
practice Rc and RefCell # start a new ladder
cargo run --bin rc_refcell # run it β€” passes the solved rungs, stops at your next todo!
```

## Quick start

```bash
# run the scratch pad (quick throwaway code)
cargo run

# run a specific concept ladder
cargo run --bin cow

# start a fresh concept (in Claude Code)
# "practice lifetimes" or /rust-practice lifetimes
```

## How a concept file works

One file per concept. Each problem is a function you implement + a `check_N()`
that asserts it. `main` runs them in order, so it replays everything you've
solved and stops at the first unfinished rung (its `todo!` panics).

```rust
fn solve_it(...) -> ... {
todo!("your turn") // <- you fill this in
}

fn check_3() { /* asserts solve_it works */ }

fn main() {
check_1();
check_2();
check_3(); // <- run stops here until you implement rung 3
// check_4();
}
```

## The curriculum

[`ROADMAP.md`](./ROADMAP.md) is a 9-phase path from comfortable-with-basics to
advanced Rust engineer. Each item is a ladder to run:

| Phase | Theme |
|-------|-------|
| 0 | Tooling, project structure & testing (features, property tests, fuzzing) |
| 1 | Ownership, conversions & the type system (smart pointers, lifetimes, variance, HRTB) |
| 2 | Traits & generics like a library author (object safety, GATs, const generics) |
| 3 | API & error design (typestate, semver, strings, collections) |
| 4 | Concurrency (atomics, memory ordering, lock-free, rayon) |
| 5 | Async internals (`Future`, `Pin`, build an executor, tower) |
| 6 | Unsafe & the machine (raw pointers, layout, Stacked Borrows, implement `Vec`) |
| 7 | Performance & low-level craft |
| 8 | Metaprogramming (declarative & proc macros) |
| 9 | Specialization tracks (no_std/embedded Β· web Β· WASM Β· CLI) |
| 10 | Capstones |

~90 ladders Β· ~700 hands-on problems.

Say **"what's next"** to pull the next unchecked item and start its ladder.

## Completed concepts

**Phase 0 β€” Tooling, project structure & testing**

| Concept | File | Rungs |
|---------|------|-------|
| Modules & visibility | [`src/bin/modules.rs`](./src/bin/modules.rs) | 9 β€” module tree & paths β†’ `pub` opens a door β†’ field privacy + smart constructor β†’ `use` vs `pub use` re-export β†’ leaking a private type (E0603/E0616) β†’ `pub(crate)`/`pub(super)`/`pub(in path)` β†’ facade pattern β†’ sealed trait via private module β†’ `inventory` mini-library capstone |
| Cargo features & `cfg` | [`src/bin/features_cfg.rs`](./src/bin/features_cfg.rs) | 9 β€” `cfg!()` macro β†’ `#[cfg]` attribute (twin defs) β†’ first feature flag β†’ `cfg_attr` (serde idiom) β†’ **additivity law** (collide vs accumulate) β†’ missing-symbol trap (gate the call site too) β†’ optional dep `dep:rand` β†’ feature graphs (`default`/`full`/`--no-default-features`) β†’ mini config/output module capstone |
| πŸ”₯ Testing | [`src/bin/testing.rs`](./src/bin/testing.rs) + [`practice/testing_lab/`](./practice/testing_lab/) | 9 β€” `#[cfg(test)] mod tests`/`assert_eq!` β†’ assertion toolbox + custom messages β†’ `Result`-returning tests & `?` β†’ `#[should_panic(expected=…)]` β†’ execution control (`#[ignore]`/filter/`--nocapture`/parallelism) β†’ assertion footguns (loose should_panic, float eq, private reach) β†’ integration tests (`tests/`, `common/mod.rs`, E0603 boundary) β†’ doctests (`?`/hidden `#`/`should_panic`/`compile_fail`) β†’ capstone `Ledger` tested every way |

**Phase 1 β€” Ownership, conversions & the type system**

| Concept | File | Rungs |
|---------|------|-------|
| `Cow` (Clone-on-Write) | [`src/bin/cow.rs`](./src/bin/cow.rs) | 9 β€” basics β†’ serde zero-copy β†’ reimplement from scratch |
| `Box` & the heap | [`src/bin/box_heap.rs`](./src/bin/box_heap.rs) | 9 β€” recursive types β†’ `dyn Trait`/`Box::leak` β†’ hand-rolled linked list |
| `Rc` / `Arc` | [`src/bin/rc_arc.rs`](./src/bin/rc_arc.rs) | 9 β€” shared ownership β†’ cycles & `Weak` β†’ `Arc` β†’ hand-rolled `MyRc` |
| `Cell` / `RefCell` | [`src/bin/cell_refcell.rs`](./src/bin/cell_refcell.rs) | 9 β€” interior mutability β†’ borrow panics β†’ hand-rolled `MyRefCell` |
| Conversion traits | [`src/bin/conversions.rs`](./src/bin/conversions.rs) | 9 β€” `From`/`Into` β†’ `TryFrom` β†’ `AsRef` β†’ mini `serde_json::Value` |
| Lifetimes in depth | [`src/bin/lifetimes_depth.rs`](./src/bin/lifetimes_depth.rs) | 9 β€” elision β†’ structs β†’ outlives bounds β†’ hand-rolled `StrSplit` |
| `Borrow` / `ToOwned` | [`src/bin/borrow_toowned.rs`](./src/bin/borrow_toowned.rs) | 9 β€” `HashMap::get(&str)` β†’ contracts β†’ hand-rolled `MyCow` |
| `Drop` & ordering | [`src/bin/drop_ordering.rs`](./src/bin/drop_ordering.rs) | 9 β€” LIFO/field order β†’ drop flags β†’ rollback-on-drop `Transaction` |
| `Rc>` patterns | [`src/bin/rc_refcell.rs`](./src/bin/rc_refcell.rs) | 10 β€” shared cell β†’ cycle leak + `Weak` β†’ doubly-linked list w/ iterative Drop |
| HRTB β€” `for<'a>` | [`src/bin/hrtb.rs`](./src/bin/hrtb.rs) | 9 β€” implicit `for<'a>` β†’ `DecodeOwned` β†’ parser-combinator capstone |

**Phase 2 β€” Traits & generics like a library author**

| Concept | File | Rungs |
|---------|------|-------|
| Generic bounds & `where` clauses | [`src/bin/generic_bounds.rs`](./src/bin/generic_bounds.rs) | 9 β€” bounds β†’ `?Sized` β†’ blanket impls β†’ `IterExt` extension trait |
| Associated types vs generic params | [`src/bin/assoc_vs_generic.rs`](./src/bin/assoc_vs_generic.rs) | 9 β€” one-impl-per-type vs many β†’ `dyn` assoc pinning β†’ `MyIterator` + `Map` |
| Blanket impls & coherence | [`src/bin/blanket_coherence.rs`](./src/bin/blanket_coherence.rs) | 9 β€” `From`β†’`Into` β†’ orphan rule β†’ sealed extension trait |
| Static vs dynamic dispatch | [`src/bin/dispatch.rs`](./src/bin/dispatch.rs) | 9 β€” monomorphization vs vtable β†’ object safety β†’ static/dynamic/enum pipeline |
| Closures & `Fn`/`FnMut`/`FnOnce` | [`src/bin/closures.rs`](./src/bin/closures.rs) | 9 β€” capture modes β†’ `Fn βŠ‚ FnMut βŠ‚ FnOnce` β†’ desugar by hand β†’ `impl Fn` vs `Box` β†’ fn-pointer coercion β†’ `Box` event dispatcher |
| `impl Trait` & RPIT | [`src/bin/impl_trait.rs`](./src/bin/impl_trait.rs) | 9 β€” APIT (caller picks) vs RPIT (callee picks one hidden type) β†’ turbofish footgun β†’ return closures/chains β†’ one-type rule (Box/Vec/`Either`) β†’ 2024 lifetime auto-capture + `use<>` β†’ `async fn` ≑ `-> impl Future` β†’ RPITIT & async-fn-in-trait (not dyn-safe) β†’ combinator toolkit |
| Marker & auto traits | [`src/bin/marker_auto_traits.rs`](./src/bin/marker_auto_traits.rs) | 9 β€” marker permissions β†’ `Copy`/`Sized`/`?Sized` β†’ structural `Send`/`Sync` β†’ opt-out/opt-in with `PhantomData` and `unsafe impl` β†’ sealed typestate capstone |

**Phase 3 β€” API & error design**

| Concept | File | Rungs |
|---------|------|-------|
| Error handling architecture | [`src/bin/error_arch.rs`](./src/bin/error_arch.rs) | 9 β€” `Box` β†’ `thiserror`/`anyhow` β†’ mini-anyhow |
| Custom error types | [`src/bin/custom_errors.rs`](./src/bin/custom_errors.rs) | 9 β€” Display+Error by hand β†’ source chains β†’ `Report` reporter |
| Newtype & zero-cost wrappers | [`src/bin/newtype.rs`](./src/bin/newtype.rs) | 9 β€” distinct identity β†’ `repr(transparent)` β†’ phantom-typed `Id` |
| Builder pattern | [`src/bin/builder.rs`](./src/bin/builder.rs) | 8 β€” consuming/`&mut` builders β†’ typestate builder β†’ `ServerConfig` |
| The typestate pattern | [`src/bin/typestate.rs`](./src/bin/typestate.rs) | 9 β€” ZST markers β†’ sealed states β†’ TCP-like protocol |
| API evolution & semver | [`src/bin/semver.rs`](./src/bin/semver.rs) | 9 — what breaks → `#[non_exhaustive]` → sealed traits → `ApiChange→Bump` engine |
| Collections deep-dive | [`src/bin/collections.rs`](./src/bin/collections.rs) | 9 β€” `Entry`/`Borrow` lookup β†’ custom `Hash`/`Eq` β†’ open-addressing `MyHashMap` |
| Strings & text | [`src/bin/strings_text.rs`](./src/bin/strings_text.rs) | 9 β€” `str`/`String` & UTF-8 invariant β†’ char-boundary slicing β†’ `OsStr`/`Path`/`CStr` β†’ `from_utf8` validation β†’ hand-rolled UTF-8 decoder |

**Phase 4 β€” Concurrency**

| Concept | File | Rungs |
|---------|------|-------|
| Threads & scoped threads | [`src/bin/threads.rs`](./src/bin/threads.rs) | 9 β€” `spawn`/`join` β†’ `thread::scope` β†’ `parallel_map` (rayon-lite) |
| `Send` & `Sync` deeply | [`src/bin/send_sync.rs`](./src/bin/send_sync.rs) | 9 β€” auto-derivation β†’ the four quadrants β†’ hand-rolled `SpinLock` |
| `Mutex` / `RwLock` | [`src/bin/mutex_rwlock.rs`](./src/bin/mutex_rwlock.rs) | 9 β€” guard RAII β†’ `Arc` counter β†’ `RwLock` readers-xor-writer β†’ poisoning & recovery β†’ non-reentrancy β†’ ABBA deadlock + lock ordering β†’ `Condvar` queue β†’ concurrent `Bank` |
| Channels | [`src/bin/channels.rs`](./src/bin/channels.rs) | 9 β€” `mpsc` send/recv β†’ multi-producer `tx.clone()` β†’ receiver-as-iterator (drop the sender) β†’ `sync_channel` bounded/rendezvous backpressure β†’ the hang + `RecvError`/`SendError` β†’ `try_recv` Empty vs Disconnected β†’ worker pool (`Arc>`) β†’ crossbeam mpmc + `select!` β†’ hand-rolled `Channel` (`Mutex`+`Condvar`) |
| Data parallelism with `rayon` | [`src/bin/rayon_parallel.rs`](./src/bin/rayon_parallel.rs) | 9 β€” `par_iter().sum()` β†’ `map`/`filter`/`collect` (order preserved) β†’ `reduce`/`fold` (identity closure, fold-then-reduce) β†’ `rayon::join` fork-join primitive β†’ break-even: when parallel LOSES vs WINS β†’ non-associative reduce = silent non-determinism β†’ shared-state wall (`for_each` push won't compile; `collect` vs `Mutex`) β†’ `par_sort` + `par_bridge` β†’ capstone: hand-rolled `parallel_map` + parallel quicksort (`split_at_mut` + `join`) |
| Shared state vs message passing | [`src/bin/concurrency_models.rs`](./src/bin/concurrency_models.rs) | 9 β€” same counter via `Arc` and mpsc aggregator β†’ ownership transfer = no lock (Mutex guards the QUEUE, not the DATA) β†’ pipeline of stages (EOF cascades as senders drop) β†’ fan-out/fan-in: shared `VecDeque` vs mpsc queue (channel gives blocking + EOF free) β†’ lock held too long (87ms vs 3ms; fat critical section serializes N threads to one) β†’ message-passing footguns (stray sender hang, `RecvError`, `sync_channel` full, the `Arc`-through-channel aliasing trap) β†’ the actor (one owner of a HashMap, command + one-shot reply, no lock) β†’ hybrid (writes via actor, lock-light reads from a published `Arc>>` snapshot) β†’ capstone: one `KvStore: Send+Sync` trait, two impls (`Arc>` vs actor), same 8-thread storm passes both β€” actor serializes at the queue, lock at the critical section |

_(early standalone demos, not ladders: `src/bin/lifetimes.rs`, `src/bin/traits.rs`)_

## Layout

```
rust-scratch/
β”œβ”€β”€ README.md ← you are here
β”œβ”€β”€ ROADMAP.md ← the mastery curriculum
β”œβ”€β”€ CLAUDE.md ← how Claude Code should drive this project
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ .claude/
β”‚ β”œβ”€β”€ skills/rust-practice/ ← the practice-ladder coach
β”‚ └── commands/ ← /experiment, /run, /explain, /list
└── src/
β”œβ”€β”€ main.rs ← scratch pad (cargo run)
└── bin/ ← one file per concept ladder
```

## Slash commands

| Command | Does |
|---------|------|
| `/rust-practice ` | start a coached practice ladder |
| `/stats` | show the gamified dashboard β€” rank, XP, phases, badges, streak |
| `/list` | show ladders & per-concept progress |
| `/run ` | run an experiment and explain its output |
| `/explain ` | tutor-mode walkthrough of an existing file |
| `/experiment ` | scaffold a one-off demo (not a ladder) |

## Preinstalled dependencies

So ladders never need setup: `tokio`, `futures`, `async-trait`, `anyhow`,
`thiserror`, `serde` + `serde_json`, `reqwest`, `rand`, `tracing`. Add more in
`Cargo.toml` as a concept needs them.