Before You Start
Understand what problem Rust actually solves before writing a line of code. These two lessons take about an hour and explain why the rest of this course looks the way it does.
What is Rust?
Origin at Mozilla, and the memory-safety problem it was built to solve
Rust was created by Graydon Hoare at Mozilla Research and reached 1.0 in 2015, aimed squarely at a problem C and C++ never solved: memory safety without a garbage collector. Understanding that goal explains the borrow checker before you ever meet it — it is not an obstacle bolted onto the language, it is the language.
Rust's origin story, the memory-safety guarantees it makes, where it runs in production, and how it compares to C++ and Go.
Setting Up Rust
Installing rustup, Cargo, and running your first program
rustup installs and manages the Rust toolchain, and Cargo — Rust's build tool and package manager — is used for nearly everything from the first day. Getting both installed correctly, plus rust-analyzer in your editor, is this lesson's entire job.
Installing Rust via rustup, the toolchain layout, cargo new/run/build, and editor setup with rust-analyzer.
Language Fundamentals
The core syntax and built-in types you will use in every Rust program. Six lessons — do not rush string handling, since it is where Rust's rules first feel different from other languages.
Variables & Mutability
Immutable by default, and the mut keyword
Every Rust variable is immutable unless you explicitly write `mut`. This is not a style preference — it is the first piece of a language-wide bias toward making mutation visible, which the borrow checker later builds on.
let vs let mut, shadowing, constants, and Rust's static, strongly-typed, inferred type system.
Data Types
Scalars, compound types, and Rust's fixed-width integers
Rust's integers have explicit widths (i32, u64, and so on) and the compiler will not silently convert between them. Combined with tuples and fixed-size arrays, this lesson covers the building blocks every later type is made from.
Integer and float types, booleans, chars, tuples, arrays, and type inference and annotation.
Control Flow
if as an expression, loop, while, for, and labeled breaks
if is an expression in Rust, so it can produce a value directly into a let binding — no ternary operator needed. `loop` is an intentional infinite loop that can also return a value via break, which for and while cannot.
if/else as expressions, loop/while/for, break with a value, and loop labels for nested loops.
String Handling
String vs &str, and why you can't index a string by position
Rust guarantees every String and &str is valid UTF-8, which is exactly why `s[0]` does not compile — a byte index could land in the middle of a multi-byte character. This lesson is where Rust's rules first feel stricter than most languages, for a genuine safety reason.
String vs &str, UTF-8 guarantees, concatenation, slicing by byte range, and the chars()/bytes() iterators.
Structs
Grouping related data, and the three struct forms
Structs are how Rust models data, in the same role classes play elsewhere but with no inheritance. This lesson covers named-field, tuple, and unit structs, plus the impl block where methods are attached separately from the data.
Named-field, tuple, and unit structs, the impl block, associated functions vs methods, and the Debug trait.
Enums & Pattern Matching
Algebraic data types, and match's exhaustiveness check
Rust enums can carry different data per variant — Option<T> and Result<T, E> are both ordinary enums defined this way. `match` forces you to handle every variant, which is how Rust eliminates an entire category of null-reference and unhandled-case bugs at compile time.
Enum definitions with data-carrying variants, match and if let, exhaustiveness checking, and Option<T>.
Ownership — Rust's Defining Feature
This is why Rust exists. Ownership, borrowing and lifetimes are enforced entirely at compile time and are what let Rust guarantee memory safety with no garbage collector. Take these five lessons slowly — nothing later in the language makes sense without them.
Ownership & Borrowing
One owner per value, and borrowing instead of copying
Every value in Rust has exactly one owner, and when that owner goes out of scope the value is dropped automatically — no garbage collector, no manual free, no double-free. Borrowing lets code use a value without taking ownership of it, and this single rule is the foundation everything else in this stage builds on.
Ownership rules, move vs copy, the Drop trait, borrowing with & and &mut, and the aliasing rules the compiler enforces.
References & Slices
Borrowed views into data, without copying it
A reference lets a function read or modify data it does not own, and a slice is a reference to a contiguous run of a collection — a string slice or an array slice — without owning or copying the underlying data.
Reference syntax, dereferencing, string slices and array slices, and slices as function parameters.
Lifetimes
Compiler-checked proof that references don't outlive their data
A lifetime is not a runtime concept — it is the compiler's proof, checked entirely at compile time, that a reference never outlives the data it points to. Most of the time lifetimes are inferred silently; this lesson is about the cases where you must name them explicitly.
Lifetime annotation syntax, lifetime elision rules, lifetimes in structs, and the 'static lifetime.
The Borrow Checker, Deep Dive
Why it rejects code, and how to work with it
The borrow checker is the compiler pass that enforces ownership and borrowing rules, and its errors are usually pointing at a real bug in your reasoning, not an arbitrary restriction. This lesson works through the classic rejected patterns and the idiomatic ways to restructure code so they compile.
Common borrow-checker rejections, non-lexical lifetimes, splitting borrows, and interior mutability as an escape hatch.
Smart Pointers
Box, Rc, and RefCell — managing heap data without a GC
Ordinary references cover most cases, but some data needs to live on the heap (Box), be shared by multiple owners (Rc), or be mutated through a shared reference (RefCell). These smart pointer types are how Rust handles the remaining cases ownership alone cannot express.
Box<T> for heap allocation, Rc<T> for shared ownership, RefCell<T> and interior mutability, and the Deref/Drop traits.
Generics, Traits & Collections
Rust replaces inheritance with traits and generics, and ships a small but sharp set of collection types. Six lessons, ending with closures and iterators — the two features that make idiomatic Rust read more like a functional language than a systems one.
Generics
Type parameters, checked at compile time with zero runtime cost
Generics let one function or struct work over many types, and because Rust monomorphizes generics — generating a specialized copy per concrete type at compile time — there is no runtime overhead at all, unlike generics in many other languages.
Generic functions, structs and enums, trait bounds on type parameters, and monomorphization.
Traits & Trait Objects
Shared behavior, and dyn Trait's runtime dispatch
A trait defines behavior a type can implement, similar to an interface, and trait bounds let generic code require that behavior at compile time. `dyn Trait` is the other option — trading that compile-time speed for the runtime flexibility of storing different types behind one pointer.
Defining and implementing traits, default methods, trait bounds, impl Trait, and dyn Trait / trait objects.
Types & Traits, Tied Together
How structs, enums, traits and generics compose into one system
You have now met structs, enums, generics and traits separately. This lesson is the synthesis — seeing how they combine into Rust's actual answer to object-oriented design, without classes or inheritance anywhere in the picture.
How structs, enums, impl blocks, pattern matching, Option, traits, generics and dyn Trait fit into one coherent model.
Collections
Vec, HashMap, HashSet, and when to reach for each
Rust's standard collections cover most real programs: Vec<T> for growable lists, HashMap<K, V> for lookups, and HashSet<T> for uniqueness. Each interacts with ownership in a specific way worth seeing before you use them in real code.
Vec<T> operations, HashMap<K, V> and the entry API, HashSet<T>, and iterating collections without taking ownership.
Closures
Functions that capture their environment, and the three Fn traits
A closure can capture variables from its surroundings, and Rust expresses exactly how — by reference, by mutable reference, or by move — through the Fn, FnMut and FnOnce traits. Getting this straight explains a class of closure-related compiler errors.
Closure syntax, capturing by reference vs by move, the Fn/FnMut/FnOnce traits, and closures as function parameters.
Iterators
Lazy, zero-cost chains — map, filter, and collect
Iterator chains in Rust compile down to the same machine code as a hand-written loop — a textbook zero-cost abstraction. This lesson covers the Iterator trait itself and the adapters (map, filter, fold) you will reach for constantly in idiomatic Rust.
The Iterator trait, common adapters (map, filter, fold, zip), laziness, and implementing Iterator for your own type.
Error Handling & Concurrency
Rust has no exceptions and no garbage-collected runtime backstopping thread safety — both are handled at compile time instead. Five lessons covering Result, threads, async, and the boundary where you can deliberately step outside the compiler's guarantees.
The Result Type
Result<T, E>, and how ? turns error propagation into one character
Result<T, E> replaces exceptions: a function that can fail returns Result instead of throwing. The ? operator then propagates an Err straight out of the current function, turning what would be several lines of error-checking into a single character at the call site.
Result<T, E> and Option<T>, the ? operator, combinators like map/and_then, and unwrap vs proper handling.
Concurrency & Threads
std::thread, message passing, and Send/Sync enforced by the compiler
Rust's marketing calls this "fearless concurrency" for a specific reason: the Send and Sync traits let the compiler reject a data race before the program ever runs, rather than catching it at runtime or not at all. This lesson covers spawning threads, message passing, and shared state.
std::thread::spawn, message passing with mpsc channels, Arc<Mutex<T>> for shared state, and the Send/Sync marker traits.
Async/Await
Futures, .await, and why Rust needs a runtime like Tokio
Rust's async functions return a Future that does nothing until polled — unlike JavaScript's promises, which start running immediately. That laziness is why async Rust needs an executor such as Tokio to actually drive futures to completion.
async fn and .await, the Future trait, why an executor is required, and async vs threads for I/O-bound work.
Error Handling & Concurrency, Tied Together
Result, ?, panic, threads and channels in one realistic program
This lesson is the synthesis for this stage: propagating a Result with ? through a function that also spawns threads and shares state behind Arc<Mutex<T>>, plus the panic/threading interaction — what happens to the rest of a program when one thread panics.
Custom error types, panic vs Result, propagating errors across thread boundaries, and Arc/Mutex/mpsc in combination.
Unsafe Rust
The five things unsafe unlocks, and the safety contract you take on
`unsafe` does not turn off the borrow checker; it unlocks exactly five capabilities the compiler cannot otherwise verify, such as dereferencing a raw pointer. Every unsafe block is a promise from you to the compiler, and idiomatic Rust wraps unsafe code in a safe API almost immediately.
Raw pointers, calling unsafe functions, mutable statics, unsafe traits, union fields, and safe abstractions over unsafe code.
Tooling, Performance & Shipping
Cargo, testing and the standard library are what turn correct Rust into a shippable project. Six lessons on organizing, testing, and understanding the performance model underneath it all.
Performance & Memory
Zero-cost abstractions, and matching hand-written C
Rust's high-level features — iterators, generics, closures — are designed to compile away entirely, so idiomatic Rust routinely matches hand-written C performance. This lesson is about why that promise holds, and the stack/heap model underneath it.
Stack vs heap allocation, zero-cost abstractions, monomorphization's role in performance, and profiling basics.
Macros
Code that writes code — vec! and println! are macros, not functions
Rust macros run at compile time and can generate code, which is why vec![] can take any number of arguments when ordinary functions cannot. This lesson covers declarative macros (macro_rules!) and gives you enough to recognize derive macros like #[derive(Debug)] for what they are.
Declarative macros with macro_rules!, derive macros, attribute macros, and when a macro is the right tool over a function.
Modules & Crates
Organizing code, and Rust's privacy-by-default rule
Rust items are private to their module by default; you opt in to visibility with `pub`, which is the inverse of many languages' public-by-default habit. This lesson covers the mod/use system and how a project is split across multiple files.
Module declarations, the pub keyword, use and re-exports, and splitting a crate across multiple files.
Cargo & Packages
Cargo.toml, crates.io, and semantic versioning
Cargo builds, tests, documents and publishes Rust code, and Cargo.lock gives every build reproducible dependency versions. This lesson covers adding dependencies from crates.io, workspaces, and Cargo's command surface beyond build and run.
Cargo.toml and Cargo.lock, adding dependencies, semantic versioning, workspaces, and cargo's built-in commands.
The Standard Library Tour
std — collections, IO, the filesystem, and environment access
Rust's std is deliberately smaller than Go's or Python's, pushing things like async runtimes and random number generation out to crates.io. This lesson maps what actually lives in std, so you know when to reach for it before adding a dependency.
std::collections, std::io and std::fs, std::env, std::process, and what std intentionally leaves out.
Testing
#[test], integration tests, and cargo test
Testing ships with Cargo — no separate framework required for the basics. This lesson covers unit tests in the same file as the code, integration tests in a separate tests/ directory, and the assert! family of macros that make a failing test point straight at the problem.
#[test] and #[cfg(test)], assert!/assert_eq!/assert_ne!, integration tests in tests/, and cargo test's output.
Concepts That Cross Languages
These pages explain ideas that are not specific to Rust — they apply to every language you will ever learn. Read them once and the next language costs you far less effort. They pair well with the lessons above rather than replacing them.
Build Real Things — 10 Projects
Reading is not enough. These 10 projects — five basic, five medium — are chosen because each one exercises something specific you learned above, and because they are genuinely idiomatic Rust rather than generic exercises. Each link below opens the project brief; the Rust implementation walkthrough is in progress.
Loops, stdin input, and the rand crate.
Functions, floating-point types, and match expressions.
File reading, HashMap for counting, and the String/&str split.
Structs, Vec<T>, and persisting state to a file.
The rand crate, char handling, and building a String.
HTTP requests, Result-based error propagation with ?, and regex extraction.
An async web framework, JSON via serde, and shared state behind Arc<Mutex<T>>.
Argument parsing, structs, and file persistence.
An HTTP service with storage — a complete small system.
Streaming large files with BufReader, and a HashMap-based counter.
Practice & Experimentation
Ongoing, not a final step. Use these throughout the course — try every snippet you read, and run anything you are unsure about rather than assuming.
Run Rust code without installing anything, via the Rust Playground.
Copy-ready idiomatic patterns to keep beside you while you build.
All 30 topic pages in one index, for looking things up later.