Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Problem-solving recipes

A step-by-step procedure for every problem type this course teaches — what to do first, what to do next, and how to tell which case you're in.

What are you being asked to do?

What's in front of you right now?

  • The build stopped with a borrow or move error (E0382, E0499, E0502, E0506) → Fix a borrow error. Not sure which line moved the value? Predict what happens to a value at each use first. Only asking where a value is freed? Find the line that frees each value.
  • The program panicked → Trace a panic to its cause.
  • The error is about lifetimes → E0106 on a function that returns a reference: Write out the lifetimes a signature leaves out, then Annotate a function that returns a reference if elision can't decide. E0597, E0515 or E0716, or "may not live long enough" → Fix a lifetime error, which usually means changing ownership, not annotations.
  • E0117, a trait you didn't write on a type you didn't write → Implement a foreign trait for a foreign type (via a newtype).
  • You're designing a function or a type: the same calculation appears twice → Turn repeated code into a function. It can fail → Choose how a function reports failure, then Hand an error up with ?. It stores many values → Choose a collection. A value needs the heap or several owners → Choose a smart pointer. Two copies differ only in a type → Make a function generic. Text should turn into your type → Make .parse() work on your type. A sequence built one value at a time → Make your own type an iterator. Copy-and-paste a function can't remove → Write a macro_rules! macro. The whole API → Choose a design for a Rust type or API. Porting from another language → Translate a function into Rust.
  • You're organizing a package: logic stuck in main.rs → Split a program into a library and a thin main. A new crate → Check a crate before adding it, or the reviewer's version if it's someone else's pull request. One crate needs a fix → Update one dependency on purpose. Old edition → Move a crate to edition 2024.
  • Your program talks to the world: saving a file that must never be half-written → Replace a file atomically. Settings from flags, env and a file → Layer a command-line tool's settings. The same work on every item → Split a slice's work across threads by hand. A text protocol → Serve a line protocol over TCP with threads. Several SQL statements that belong together → Group statements into a sqlx transaction. The same check or header on every route → Add code that runs around every request. Logs you can't search → Instrument a request handler.
  • You're writing tests: a test fails only sometimes → Fix a test that fails only sometimes. A rule should hold for every input → Write a property-based test. The code reads the clock or uses the network → Test code that reads the clock or uses the network. It waits on a timeout or a delay → Test a timeout or a delay without waiting for it. An axum route → Test an axum route without a network.
  • Safe Rust can't express it, or C is involved: Wrap an unsafe operation in a safe API, and Review an unsafe block for every one. Calling C → Declare and call a C function from Rust, plus Compile a C file into a Rust package if you ship the C source. C calling you → Hand a Rust object to C as an opaque handle and Export a Rust function C can call safely.
  • You're shipping it: Shrink a release binary, Put a Rust service in a small image, Set up CI for a Rust project.

When the compiler says no

Designing a function or a type

Organizing a package and its dependencies

Programs that talk to the world

When a program or a test fails

Unsafe code and C

Shipping it