Systems Programming in Rust
Learn Rust the way it actually rewires how you think about code: ownership and borrowing that make memory bugs impossible, structs and methods, enums and exhaustive pattern matching, Option and Result error handling with the ? operator, the standard collections, traits and generics, iterators and closures, and fearless concurrency with threads and channels. Memory safety and zero-cost abstractions, the hard way made approachable, ending in a capstone that wires it all together.
10 projects, 250 hands-on levels, run in your browser.
Syllabus
- Rust Foundations: Rust looks familiar but is strict in ways that pay off later: values are immutable unless you say `mut`, every binding has a known type, and almost everything is an expression that produces a value. This project builds fluency with that core, bindings and shadowing, the integer and float types and their exact rules, booleans and chars, functions and expression blocks, control flow that returns values, and the compound types (tuples, arrays, slices), the ground every later project stands on.
- Ownership & Borrowing: Every value in Rust has exactly one owner. Assigning or passing it MOVES ownership, and the compiler then forbids using the old name, which is how Rust guarantees memory safety with no garbage collector. References let you BORROW a value to read or mutate it without taking it, under rules the compiler checks. This project builds that intuition through signatures: functions that take ownership, functions that borrow immutably, functions that borrow mutably, when to clone, and slices that borrow a view, the model the rest of the language rests on.
- Structs & Methods: A struct names a bundle of related fields, and an `impl` block attaches behavior to it. This project builds the object-like core of Rust without inheritance: defining structs and constructing them, associated functions like `new`, methods that borrow `&self` to read or `&mut self` to change, the derive macros that grant `Clone` and equality for free, and composition, structs built from other structs, which is how real Rust models a domain.
- Enums & Pattern Matching: An enum is a type whose value is exactly one of several named variants, and each variant can carry its own data. This is how Rust models choice precisely: a shape is a circle OR a rectangle, a result is success OR failure. `Option` and `Result` are just enums in the standard library. `match` forces you to handle every case, so whole classes of bug vanish, and `if let` / `while let` give you the concise version. This project builds fluency with enums, Option, Result, and the full power of pattern matching.
- Error Handling: Rust has no exceptions. A function that might fail says so in its type, returning `Option` for a missing value or `Result` for a failure with a reason, and the caller must deal with it. This project builds the toolkit: the combinators that transform Option and Result without unwrapping, the `?` operator that propagates an error up in one character, custom error enums that name what went wrong, and robust functions that parse and validate input and fail cleanly. Error handling here is a value you pass around, not a control-flow surprise.
- Collections & Strings: The standard library's collections do the heavy lifting in real Rust. This project builds fluency with the three you reach for daily: the growable `Vec`, the owned `String` and its borrowed `&str` view, and the `HashMap` (plus `HashSet`) for keyed lookups and membership. You will build vectors, manipulate text, count word frequencies, sort and dedup, and combine sets, the bread-and-butter data wrangling that every program needs.
- Traits & Generics: Traits are Rust's interfaces: a set of methods a type promises to provide, so different types can be used through a shared contract. Generics let you write a function or struct once and use it with any type that satisfies the bounds you require. Together they give Rust its zero-cost abstractions. This project builds defining and implementing traits, default methods, generic functions with trait bounds, the standard traits the language is built on (Display, From, Iterator, Ord), and trait objects for runtime polymorphism.
- Iterators & Closures: Closures are functions that capture their surroundings; iterators are lazy pipelines that transform a sequence one element at a time. Together they replace most hand-written loops with clear, composable expressions, and the compiler turns them into the same fast machine code. This project builds closures and the `Fn` traits, the adapter methods that map and filter, the consuming methods that fold a sequence to a value, the chaining adapters like zip and enumerate, and the advanced ones (filter_map, partition, scan) that express real data pipelines.
- Fearless Concurrency: Rust's ownership rules extend to threads, so the compiler rejects data races before the program runs, which is why the docs call it fearless concurrency. This project builds the toolkit: spawning threads and joining their results, moving data into a thread, passing messages over channels, and sharing state safely with `Arc >`. Then it assembles those pieces into parallel patterns, splitting work across threads and combining the results, the everyday shape of using more than one core.
- Capstone: Calculator & Store Engine: The finale ties the whole track together by building two small but real engines: a stack-based calculator that tokenizes and evaluates reverse-Polish expressions with proper error handling, and an in-memory key-value store driven by parsed commands. You will use enums and pattern matching for tokens and commands, structs and methods with a HashMap for the store, Result and Option for failure, and iterators to drive it all, then assemble them into a `run` function that executes a whole program. By the end you have written a working interpreter in Rust.
Key concepts
- ? operator: On Ok / Some it unwraps the inner value; on Err / None it returns that from the enclosing function immediately. It collapses error propagation into a single ch…
- Arc: An atomically reference-counted shared pointer, Arc<T> , that lets several threads own the same value. Wrap a Mutex inside to allow safe mutation. Its si…
- Array: A fixed-length, same-type sequence [T; N] whose length is known at compile time. For a growable sequence use a Vec.
- Associated function: A function in an impl with no self parameter, called as Type::name(...) . new is the conventional constructor; Self names the type.
- Borrow checker: The part of the Rust compiler that enforces the borrowing rules: either many shared references or one mutable reference, never both, and no reference outliving…
- Borrowing: Accessing a value through a reference without taking ownership, so the owner keeps the value afterward. The borrow checker enforces the rules at compile time.
- Box: Box<T> is the simplest smart pointer: it stores a value on the heap and owns it. Used for trait objects, recursive types, and moving large values cheaply.
- Cargo: Rust's build tool and package manager: it compiles your crate, fetches dependencies from crates.io, runs tests, and manages the project, all from a Cargo.t…
- Casting (as): Explicit numeric conversion with the as keyword. Rust never converts number types implicitly; a float cast to an integer truncates toward zero, and a wider int…
- Channel (mpsc): A typed pipe between threads: a sender send s values and a receiver recv s them. mpsc means multi-producer, single-consumer; cloning the sender lets several th…
- Clone: An explicit, possibly deep, copy of a value via .clone() . Used when you need two independent owned copies of a non-Copy type like String or Vec.
- Closure: An anonymous function, |x| ... , that can capture variables from its surrounding scope. Passed as impl Fn , or returned with move so it owns its captures.
- collect: A consuming method that runs an iterator and gathers its items into a collection, the target type chosen by annotation: a Vec , a String , a HashMap , or even…
- Combinator: A method that transforms an Option or Result without unwrapping: map , and_then , filter , unwrap_or , ok_or , map_err . They let you chain fallible steps clea…
- Copy: A trait for small, fixed-size types (integers, bools, chars) that are duplicated on assignment instead of moved, so the original stays valid. Types with heap d…
- Crate: The unit of compilation and distribution in Rust: a binary or a library. Your project is a crate, and each dependency you pull in is one too.
- Default method: A trait method with a body, built on the trait's required methods. Implementing types get it for free, or override it. The classic example is is_empty defi…
- Dereference (*): The * operator reads or writes the value a reference points to. You dereference a &mut i64 to assign through it.
- derive: An attribute, #[derive(...)] , that auto-implements common traits for a struct or enum: Clone to copy, PartialEq to compare with == , Debug to print, Default ,…
- Destructuring: Pulling a compound value apart into its pieces with a pattern, as in let (a, b) = pair; or matching an enum variant's fields.
- Drop: The automatic cleanup that runs when a value's owner goes out of scope, freeing its resources. The Drop trait lets a type customize what happens.
- Dynamic dispatch: Choosing which method implementation to call at runtime via a trait object, as opposed to the static dispatch of generics. It trades a small lookup cost for fl…
- Entry API: map.entry(key).or_insert(default) looks up a key, inserting a default if absent, and returns a mutable reference, so *map.entry(k).or_insert(0) += 1 counts in…
- Enum: A type whose value is exactly one of several named variants. Unlike enums in many languages, each variant can carry its own data, making it a sum type. Option…
- Error handling: Rust has no exceptions. Fallibility is expressed in a function's return type with Option or Result, and the caller must handle it, so failures are explicit…
- Exhaustiveness: A match must cover every possible case or the code will not compile. Adding a new enum variant then forces you to handle it everywhere, a compile-time safety n…
- Expression-oriented: Almost everything in Rust produces a value: blocks, if , match , and loop are all expressions. A function returns its last expression (with no trailing semicol…
- Fearless concurrency: Rust's promise that the same ownership and borrowing rules which prevent memory bugs also prevent data races, so the compiler rejects racy code before it r…
- Fn / FnMut / FnOnce: The three closure traits: Fn reads its captures, FnMut mutates them, and FnOnce consumes them. A function bound chooses which it accepts.
- Generic: Code written once over a type parameter T so it works for many types. The compiler monomorphizes each use into a specialized copy, making generics zero-cost at…
- HashMap: A hash table mapping keys to values, HashMap<K, V> . get returns an Option , and the entry API updates-or-inserts in one step (the counting idiom).
- HashSet: A hash-based collection of unique values with fast membership tests, useful for distinct counts and set operations like intersection.
- if let / while let: Concise forms of match for a single pattern: if let Some(x) = opt { ... } runs only on a match, and while let Some(x) = v.pop() repeats as long as the pattern…
- impl block: Where you attach behavior to a type: methods that take self , and associated functions (like new ) that do not. impl Trait for Type provides a trait's meth…
- Integer overflow: In Rust, a plain + that overflows panics in debug builds rather than wrapping silently. When wraparound is intended you ask for it with methods like wrapping_a…
- Iterator: A lazy sequence produced by next() . Adapter methods (map, filter, zip, enumerate) build pipelines; consuming methods (sum, collect, fold) drive them. Implemen…
- Iterator adapter: A lazy method on an iterator that returns another iterator, such as map , filter , take , skip , zip , or scan . Nothing runs until a consuming method is calle…
- Lazy evaluation: Iterator adapters do no work when called; they only describe a computation. The work happens when a consumer like collect or sum pulls values through the chain.
- let binding: let introduces a variable. Bindings are immutable by default; add mut to allow reassignment.
- Lifetime: The span during which a reference is valid. Lifetime annotations like <'a> tell the compiler how the lifetimes of inputs and outputs relate, so it ca…
- Macro: Code that writes code, invoked with a ! like println! , vec! , or matches! . Macros run at compile time and can take a variable number of arguments, unlike fun…
- match: Pattern-matching control flow that chooses an arm based on a value's shape and binds its inner data. The compiler requires it to be exhaustive, so every ca…
- Method: A function inside an impl whose first parameter is self (or &self to borrow, &mut self to mutate). Called with a dot: value.method() .
- Module: A namespace within a crate, declared with mod , that groups and controls the visibility of items. use brings a path into scope; pub makes an item visible outsi…
- Monomorphization: How Rust compiles generics: for each concrete type a generic is used with, it generates a specialized copy. The result is as fast as hand-written code, the zer…
- Move: Assigning or passing an owned value (like a String or Vec) transfers ownership to the new binding. The compiler then forbids using the old name, ensuring a sin…
- mut: The keyword that makes a binding or reference changeable. Immutability is the default in Rust, which makes change explicit and intentional.
- Mutable reference (&mut T): A reference that allows changing the borrowed value. Only one &mut T may exist at a time, and not alongside any shared reference, which rules out data race…
- Mutex: A lock guarding shared data: lock() blocks until it is available and returns a guard that gives access and unlocks when dropped. Combined with Arc to share mut…
- Option: The enum Option<T> is Some(value) or None : Rust's replacement for null. It forces you to handle the missing case before using the value.
- Ownership: Rust's central rule: every value has exactly one owner (the variable that holds it), and when the owner goes out of scope the value is dropped and its memo…
- panic!: An unrecoverable error that unwinds the current thread, used for bugs and broken invariants. For expected failures, return a Result instead.
- Pattern: A shape matched against a value to destructure or test it: literals, ranges ( 1..=9 ), tuples, struct/enum variants, | for several, guards with if , and @ to b…
- RAII: Resource Acquisition Is Initialization: a resource is tied to a value's lifetime, acquired when the value is created and released when it is dropped. Rust…
- Reference (&T): A shared, immutable borrow of a value. Any number of &T may exist at once because reading is always safe.
- Result: The enum Result<T, E> is Ok(value) or Err(error) : a computation that can fail with a reason. Rust's recoverable error handling, used instead of exce…
- self / Self: Lowercase self is the receiver of a method (the value it is called on); capitalized Self is an alias for the type being implemented.
- Send & Sync: Marker traits the compiler uses to track concurrency safety: Send types can be moved to another thread, Sync types can be shared by reference across threads. T…
- Shadowing: Reusing a name with a new let , creating a fresh binding that hides the old one. Unlike mut , shadowing can change the value's type and keeps each binding…
- Slice (&[T]): A borrowed view into a contiguous run of elements, with a pointer and a length, so one function works on both arrays and vectors. A &str is a string slice.
- Smart pointer: A type that acts like a pointer but adds ownership semantics: Box (single owner on the heap), Rc/Arc (shared ownership), and others, often paired with a guard…
- Statement: A piece of code executed for its effect, ending in a semicolon, that does not produce a value (like a let binding). Adding a semicolon turns an expression into…
- str / &str: A string slice: a borrowed, immutable view into UTF-8 text. String literals are &'static str . Prefer &str parameters to borrow text without taking…
- String: An owned, growable, heap-allocated, UTF-8 text buffer. Build it with push_str / push ; it is to text what Vec is to elements.
- Struct: A named bundle of fields of fixed types. Construct with Name { field: value } , read with a dot. Tuple structs name fields by position instead.
- Thread: thread::spawn runs a closure on a new OS thread and returns a JoinHandle ; .join() waits for it and yields its result. A move closure hands owned data to the t…
- Trait: A contract of method signatures a type promises to provide, like an interface. Types implement a trait with impl Trait for Type , and code can be written again…
- Trait bound: A constraint on a generic type, like T: PartialOrd , stating which traits T must implement (and therefore what it can do). Multiple bounds combine with + .
- Trait object (dyn): A &dyn Trait or Box<dyn Trait> holds any type that implements the trait, chosen at runtime (dynamic dispatch). Use it for a heterogeneous collection;…
- Tuple: A fixed-length group of values of possibly different types, like (i64, f64) . Access fields by position ( .0 , .1 ) or destructure with a pattern.
- unsafe: A block or function where you take on guarantees the compiler cannot check: dereferencing raw pointers, calling foreign code, or breaking aliasing rules. Most…
- unwrap / expect: unwrap() extracts the value from an Option / Result , panicking on None / Err ; expect(msg) does the same with a custom message. Prefer match , ? , or unwrap_o…
- Variant: One of the named cases of an enum. A variant can be a unit (no data), tuple-style ( Circle(f64) ), or struct-style ( Move { x, y } ).
- Vec: A growable, heap-allocated array, Vec<T> . Push and pop at the end, index with [i] , and iterate; it owns its elements.
- Zero-cost abstraction: An abstraction that compiles down to code as efficient as if you had written the low-level version by hand. Generics, iterators, and Option all aim to add no r…