Where to Go Next

The last lesson, and it teaches no new syntax. First, a look back: the threads that ran the length of this course - lesson 22's Box<dyn Error>, lesson 27/25's move and static-lifetime closures, lesson 33's Arc<Mutex<T>> preview, and lesson 1's very first ! - each picked up exactly when it mattered, because almost nothing here was taught in isolation. Then, forward: cargo clippy and cargo fmt, two commands that belong in every project from here on; a single unsafe block - raw pointers, dereferenced - the only unsafe code this course will ever show, named back in lessons 20 and 35 but never explained until now; and a map of the wider ecosystem (clap, anyhow, thiserror, reqwest, axum, rayon) showing which lesson prepared you for each. Closes with a final project extending lesson 32's task_cli, and a callback to lesson 9's "the compiler is teaching you."

14 min read5 learning objectives

What You'll Learn

  • Identify two tools, cargo clippy and cargo fmt, and explain what each one checks that the compiler does not
  • Read a tiny unsafe block - raw pointers, dereferenced - and explain why this course used neither until now
  • Recall that the entire Rust Book and standard library docs are available offline via rustup, since lesson 3
  • Map five real crates - clap, anyhow, reqwest, axum, rayon - to the lesson that prepared you to use each one
  • Choose one concrete extension to the building-a-cli project that combines at least two ideas from the last six lessons

Thirty-seven lessons, starting from fn main() { println!("Hello, World!"); } and ending with the macro that makes it work. This last one teaches no new syntax. First, a look back - then two small tools that belong in every Rust project from here on, one thing this course deliberately never showed you, and a map of where it leads next.

A shape, in hindsight

A few threads ran the entire length of this course, each picked up exactly when it mattered. Custom-error-types wrapped errors in Box<dyn Error> and asked you to trust it; ten lessons later, smart-pointers named both halves - and previewed Arc<Mutex<T>>, which became shared-state's real code two lessons later. Closures' move and lifetimes' 'static became fearless-concurrency's real E0373 error. And lesson 1's very first ! waited 35 lessons for last lesson.

None of it was filler. Ownership underwrites every borrow-checker error since, including the Send/Sync errors two lessons ago. Option, Result, and ? underwrite every fs::read_to_string and serde_json::from_str in the practical-Rust section. Iterators are the .map/.filter/.enumerate in building-a-cli's list command, and move closures are what gets handed to every thread::spawn and tokio::spawn since. If generics, traits, and lifetimes felt like the hardest stretch the first time and easier the fifth time they came back - that was the plan, not a coincidence.

Two commands for every project

One tool has been here since lesson 3, quietly: rustup installed more than the compiler. Two of its companions never came up:

This compiles cleanly - cargo build has nothing to say about it. cargo clippy does:

Two warnings, neither one an error - the compiler was satisfied; clippy has opinions beyond "does this compile." v.len() == 0 works fine, but v.is_empty() says the same thing more directly - clippy knows the idiom. The second one is more pointed: vec![1, 2, 3], the exact macro last lesson spent an entire lesson explaining, isn't even the best tool here - this particular list never grows, so a plain array would do. Clippy knows the idiom and the exception.

cargo fmt is the other half: it reformats every file in a project to one standard style - the same style every code block in this course has used - so two Rust developers' code looks the same on sight, and nobody argues about brace placement.

The third one, rustup doc, opens a local copy of the Rust Book and the full standard library documentation in your browser - no internet required. It's been sitting on disk since lesson 3.

One thing this course never showed you

The word unsafe came up twice, in passing. Understanding-errors called it "rare in everyday application code, far more common in the systems-level Rust this language was originally built for." Shared-state, two lessons ago, ran ten threads against one counter and got Result: 10 every time, "and not one line of unsafe." Neither lesson said what it actually looks like. This is what it actually looks like:

Run it: 6. *mut i32 is a raw pointer - unlike every reference this course has used, it has no borrow-checker rules attached: no guarantee it points anywhere valid, no tracking of how long it lasts, no exclusivity check. Creating one is safe; using it - dereferencing it with *r - requires an unsafe block, Rust's way of saying "the compiler can't verify this is sound; you're asserting that it is." That's the entire feature. There's no special syntax inside the block beyond this - just a handful of operations (raw pointer access, calling other unsafe functions, a couple more) that the compiler will only allow inside one.

Reach for it almost never in application code - but it's not exotic. It's how Vec, String, Mutex, and Rc are built underneath: a small unsafe core, wrapped in a safe API, so that everything you've written could be 100% safe Rust on top of it. Now you know the word, the syntax, and roughly where it hides.

Where this course fits

rand - lesson 4's very first dependency - serde, and tokio were real crates from the wider Rust ecosystem, not built for this course. A few more, mapped to what you already know:

None of these need to be memorized - docs.rs hosts documentation for every crate on crates.io, generated by the same cargo doc from the comments lesson. When a project needs something, search there first; chances are good it already exists; reading its types and traits will look exactly like reading the standard library, because it's the same language. Beyond that: users.rust-lang.org for questions, and This Week in Rust for a weekly pulse on everything moving in the language and ecosystem.

Quick exercise

Pick one, and extend task_cli with it - you now have everything each one needs:

  1. Add clap with the derive feature, and replace the env::args match with a derived struct Cli. Same three commands, declared instead of parsed by hand.
  2. Add a search <term> command: filter tasks with .iter().filter(...) on whether the description contains the term, then reuse the list command's .enumerate() printing.
  3. Wrap the in-memory Vec<Task> in Arc<Mutex<Vec<Task>>> and save to disk on a background thread via thread::spawn, so add and done return immediately.
  4. Add axum and tokio, and serve GET /tasks as JSON straight from load_tasks - the same Task struct, #[derive(Serialize)] already done, now answering over HTTP instead of the terminal.

Lesson 9 said a borrow-checker error "doesn't just say 'you messed up' - it tells you exactly where, why, and which line is the problem," and that this is what people mean by the compiler teaching you. Every error since - through threads, futures, and macros - was that same compiler, same habit. That doesn't stop here.

Back to the course overview - or pick whichever lesson still feels fuzzy and read the compiler's explanation again. It hasn't gotten any less honest, and by now, neither have you.