Rust — Learning Library¶
A learning library for Rust, built the same way as its sibling star-voting-library ↗: one idea per page, and every claim backed by a program that actually runs.
No page here hand-types what a program prints. Each lesson links a real .rs file; a tool compiles it, runs it, checks the output against a recorded answer key, and pastes that verified output into the page. CI fails if any of the three drift apart. So when a page says "this prints Some(12)", that is not a promise — it is a test result.
📖 Read it as a site: https://masiarek.github.io/rust-learning-library/
Start here¶
New to Rust entirely? 00_Start_Here/ is the plan: which three free resources to use, in what order, and why each covers what the other two cannot.
Already writing Rust? Two lessons to begin with:
| Lesson | What it teaches |
|---|---|
| Running a scratch program | How to run a .rs file at all — rustc alone, cargo new, and the edition flag Cargo would have passed for you |
Option vs Result |
When absence is normal and when it is a failure — and the one question that decides which type you want |
01_Foundations/ is the map of everything a first week of Rust needs, now split across seven sections by subject rather than kept in one flat run: first programs, control flow, structs, Option and Result, ownership, strings and numbers and bytes. Lessons about the toolchain rather than the language — cargo, build profiles, compile times — are in 05_Tooling/; the ones that assume the foundations — shared state across threads, unsafe, FFI — in 09_Advanced/.
25_Control_Flow/ is the if, match and three loops every program is made of. It sits right after the first programs because it needs exactly one idea from them — that a block has a value — and gives back the thing that makes Rust control flow read differently from its C-shaped equivalent: these are expressions, so let size = if n < 10 { "small" } else { "large" }; needs no ternary and no mut. Stubs today, outlines with their traps written down; blocks and scopes themselves are already taught, in a block is an expression.
13_Enums/ and 12_Traits/ sit directly after the foundations, because between them they are what the rest of the language is made of. Enums come first: anyone leaving the foundations has already used several of them — Option and Result among them — without being told that the feature has a name, or that a match which forgets a variant is a build error. Traits are the other half, and the same argument applies twice over: Copy, Display, Iterator and From all turn up in the earlier pages long before anything explains what they have in common.
30_Pattern_Matching/ is one feature taught once instead of six times. match arms, if let, while let, let else, every let, and every function parameter are all the same thing — a shape matched against a value, binding the pieces by name — and what differs between them is only what happens when the shape does not fit. Stubs today. Three pieces of the topic stay where they already are, on the pages a reader meets them from: if_let, while_let, and the lowercase arm name that is not a comparison but a binding that matches everything.
22_Generics/ follows both, because it needs both: <T> is how one definition serves every type, and a trait is what tells the compiler which types are allowed. It is also the section that explains the brackets in Option<T>, Vec<T> and Result<T, E> — read a hundred times by then, and nowhere defined.
23_Closures/ and 24_Iterators/ are the pair of features every section above has already been spending. unwrap_or_else(|| 0) and for row in &rows are both on page one of this library and neither is explained there. Closures cover which of the three Fn traits a closure gets and why the compiler cares, what move moves, and why a closure that captured nothing is smaller than a function pointer; iterators cover why a chain of adapters allocates nothing, which of three doors a for loop picked for you, and what happens when you write next yourself. They are two sections rather than one because the sidebar is alphabetical, and a reader looking for iterators can name iterators.
One section is not about Rust at all. 11_Unix/ is the shell you run the compiler from — fzf for picking a file instead of typing its path, fd and rg for finding one — and it earns its place because two of those three are themselves Rust programs, measured here against the Unix tools they stand in for.
20_Compilers/ is the layer under all of it. Most of what turns a .rs file into a binary was not written for Rust — rustc parses and borrow-checks, and then LLVM optimizes, LLVM generates machine code, and a linker nobody on the Rust team maintains finishes the job. The section covers the compile-time/run-time line, what an optimizer is allowed to do to your loop, what LLVM actually is, what the linker does with the blanks rustc leaves, and the same machinery aimed backwards as obfuscation.
Five sections in between follow one long arc: turning a snippet into a program somebody else runs. 02_Errors/ is what a failure does on its way out; 03_Command_Line/ is what the program was handed on the way in; and 04_Files/, 06_Data/ and 07_Clients/ are the three things outside it that can say no. Most of those pages are stubs today — one outline and a set of questions per page, with no runnable example behind them yet, each marked as such at the top. They exist so the arc has a shape, and a permanent URL, before the prose does; a page graduates by acquiring an example and losing its notice.
21_Observability/ picks up where that arc ends: the program is now the service somebody else calls, and the only account of what happened to one request is whatever it chose to write down — a span, a structured line, a counter, and a header carrying the trace to the next process. Stubs as well, and the hardest ones here to finish, because every checked example in this library compiles with rustc alone and observability in Rust is a crate story. The section README says how it intends to close that gap rather than skip it.
RUST_BY_EXAMPLE.md is a map of a different kind: Rust by Example ↗ chapter by chapter, with the lesson here that covers each one, and an honest list of what it does not.
Five topics are big enough to have a map of their own rather than a single page: OPTION.md, SHADOWING.md, STRUCTS.md, STRINGS.md and TOOLCHAIN.md each collect every lesson on their subject in reading order, and say what the idea is before the syllabus starts. The first four cross section boundaries because the lessons do; the fifth sorts one section by the problem you actually have.
ERRORS.md is a map you arrive at from the opposite direction: not "what shall I learn next" but "rustc printed E0502 and I am stuck". It lists the 58 error codes this library teaches with the lesson that explains each one, and it is honest about the two that have no single home — E0308 most of all, which is what rustc says when almost anything is wrong.
A lesson can end with a ## Po polsku section — the same idea framed the way Polish-language material frames it, with the Polish terminology and the exact phrases to search for. It is written in Polish rather than translated into it, and it keeps the English term in sight, because rustc and every crate's documentation speak English. POLSKI.md holds the terminology table and says which terms are anchored in Tour of Rust's Polish chapters ↗ and which this library had to choose for itself. Ownership has the sections on every page; the rest of the library does not have them yet.
Every lesson explains; some of them also ask you to type. Those exercises are collected in KATAS.md, which is the only place they are ordered — each kata itself sits on the page for the topic it teaches, with a solution CI compiles and runs.
There is also a slow, optional thread running through them: the long way round to a column summary, which sequences a handful of lessons so that each one is the next thing Rust wants to teach, and the running example happens to be a small file-summarising tool. Every rung stands alone; the tool is the excuse.
What is not written yet has its own list: TODO.md holds the strings vocabulary — 198 terms a reader meets in real Rust string code — with a box per term, ticked when the term is answerable from a lesson or a glossary entry rather than when it has a folder of its own.
The course, in order¶
The sidebar is sorted alphabetically, because that is how you find a section you can already name. This is the other question — what should I read next? — and it is the order these were written to be read in:
| # | section | what it is for |
|---|---|---|
| 1 | Start here | The three outside courses this library is a companion to |
| 2 | Foundations | The map of a first week, pointing into the seven sections below |
| 3 | First programs | Running a .rs file at all, and what the punctuation in it means |
| 4 | Control flow | if, match and the three loops — the ones that have values |
| 5 | Structs | A compound type of your own, before the two the library leans on |
| 6 | Option and Result |
The two enums everything returns, and the dozen ways to open them |
| 7 | Ownership | Who owns the value — moves, borrows, and what a shadow does |
| 8 | Strings | Text: the owner and the view, and the bytes underneath |
| 9 | Numbers and bytes | The unit all of that is counted in, down to the float that cannot hold your value |
| 10 | Collections | The six types a program is made of — and why a function should take &[T], not &Vec<T> |
| 11 | Pattern matching | A pattern is a shape, not a comparison — one feature wearing six syntaxes |
| 12 | Enums | The feature Option and Result were made of all along |
| 13 | Traits | The other half the language is built from — and how a call reaches one |
| 14 | Generics | <T>: one definition per idea, instead of one per type it is used with |
| 15 | Closures | A function that carries values with it, and the three traits that say how often you may call it |
| 16 | Iterators | The sequence that computes nothing until somebody asks, and the three doors onto a collection |
| 17 | Errors | What a failure does on its way out of a program |
| 18 | Command line | What the program was handed on the way in |
| 19 | Files | The filesystem — the first thing outside the program that can say no |
| 20 | Tooling | cargo and the rest of the toolchain, rather than the language |
| 21 | Data | Serialization, and the round trip through JSON |
| 22 | Clients | The network — the last thing outside the program that can say no |
| 23 | Observability | What the service says about itself, once somebody else depends on it |
| 24 | Interfaces | Putting a face on it |
| 25 | Advanced | What needs the foundations: threads, unsafe, FFI |
| 26 | Compilers | The layer under all of it, three quarters of which is not Rust |
| 27 | Resources | Books, essays and exercises outside this library |
| 28 | Unix | The shell you run the compiler from — two of its three tools are Rust |
| 29 | C and C++ | Nine bugs the compiler refuses to build, and C++'s own two replies — read it any time after Ownership |
| 30 | Debugging | The map to what is left over once the compiler has caught the rest — read it the first time something surprises you |
| 31 | Time and benchmarking | Two clocks, a duration that cannot go negative, and the hint that keeps a benchmark honest — the Rust twin of the C++ library's first chapter; read it any time after Compilers |
Nothing enforces this order and no page depends on it; skipping around is fine. It is here because a sidebar can be sorted one way only, and A–Z answers the more common question.
Skipping around is fine; moving a row is not always. Nothing enforces the order mechanically, but parts of it are load-bearing prose: row 9 opens "The unit all of that is counted in", a back-reference to the bytes in row 8, and row 8 follows Ownership because the strings arc opens on "the pair of types the ownership pages were secretly about all along". A section's own README is the other constraint — what it links to as where it goes next has to come after it. Check those before inserting or reordering, because none of them is a link and mkdocs --strict cannot see any of it.
How the library works¶
17_Option_and_Result/
option_vs_result/
README.md the lesson (prose + code + a generated output block)
examples/
option_vs_result.rs the program the lesson is about
option_vs_result.out its recorded output — the answer key
tools/run_examples.py compiles, runs, compares, and refills the pages
A lesson marks the spot where output belongs and lets the tool fill it:
Inside the markers is generated; outside is yours. Run the tool after any change:
Running things¶
| Task | Command |
|---|---|
| Verify every example and refresh the pages | python3 tools/run_examples.py |
| Accept new output as the answer key | python3 tools/run_examples.py --update |
| Check without writing (what CI runs) | python3 tools/run_examples.py --check |
| Preview the site locally | uv run --group docs mkdocs serve |
| Run one example by hand | rustc --edition 2024 path/to/example.rs -o /tmp/x && /tmp/x |
Give an IDE a Cargo.toml to read |
python3 tools/write_cargo_toml.py |
Only rustc and Python 3.11+ are needed for the examples; uv is needed only to preview the site.
That last row is for reading the library rather than running it. Every example is compiled on its own by bare rustc, so the repo carries no Cargo.toml — and without one RustRover opens every .rs file under "Project not associated with a Cargo.toml file": no inferred types, no go-to-definition, no Run button. tools/write_cargo_toml.py writes a gitignored manifest for your machine listing every .rs file in the checkout, scratch files included, as a binary; attach it once in RustRover, or point rust-analyzer at it from VS Code or Zed.
Adding a lesson¶
See CONTRIBUTING.md — it is short, and it is mostly about the one rule above.