Part 5 · Unsafe & real code
Reading a real crate
A repeatable procedure for understanding code you have never seen, built on the one model of program comprehension that actually tells you what to look at.
What this lesson is trying to break (2)
- You understand a codebase by reading it from the top of each file.
- Understanding means understanding every line.
This is the course’s stated goal, made concrete: you are handed a crate you have never seen and asked what it does and whether to depend on it.
The model
Program comprehension has been studied for forty years, and the most useful result for our purposes is Carsten Schulte’s Block Model. It says understanding a program is not one activity but a grid, and that people get stuck because they read in one cell and never move.
Varies: the two independent axes — granularity down, perspective across
| Granularity | Text surface what it says | Program execution what it does | Function / purpose what it is for |
|---|---|---|---|
| Atoms keywords, expressions | &mut self, ?, impl | this borrows exclusively; this returns early | rarely meaningful alone |
| Blocks functions, impls | a function signature | what one call does | the job this function has |
| Relations how blocks connect | the trait a type implements | the call graph, who owns what | the design — why these pieces |
| Macro structure the whole crate | the module tree | the lifecycle of a request | the problem it solves |
Beginners live in the top-left cell and read downward. Experts start bottom-right and move down-left only when they need to. That is the difference this lesson is trying to install.
The practical instruction: start at the bottom-right and work up-left. Understand the purpose before the structure, the structure before the functions, the functions before the syntax. Reading a file from line 1 gives you atoms with nothing to attach them to, which is why it feels like effort and produces nothing.
The procedure
1. Purpose (macro / function). Read the README and the docs.rs front page. Answer in one sentence: what problem does this solve, and for whom? If you cannot, stop and find out — everything else is wasted until you can.
2. Shape (macro / text). Open src/lib.rs. Do not read the code; read the
mod and pub use lines. That is the author’s own map of the crate. The
re-exports tell you which types matter — anything re-exported at the root is
part of the intended public surface.
3. Nouns (blocks / purpose). On docs.rs, look at Structs and Enums. What
things exist? Usually two or three matter and the rest are supporting. For
serde: Serializer, Deserializer. For tokio: Runtime, JoinHandle.
4. Verbs (relations / purpose). Look at Traits. In Rust, traits are the design — they say what the crate expects you to plug in and what it promises. A crate’s central trait is usually its whole idea in one page.
5. One path (relations / execution). Pick the single most typical use — the
first example in the README — and follow it end to end. Use docs.rs’s
source links to jump into implementations. Follow exactly one path and resist
every branch.
6. Details (atoms). Only now, and only where you need them.
Tools
Do not read Rust in a plain text view. The tooling is unusually good and it does most of the work.
- docs.rs — every published crate, every version, with a
sourcelink on every item. Reading the source in context of its documentation is far better than reading the repo. cargo doc --open— the same for your project including your private items and your exact feature flags.cargo tree— why is this dependency here? Alsocargo tree -i <crate>to invert and find who pulled it in.cargo expand— see through macros. Essential for anything using derives or attribute macros heavily.- rust-analyzer — go-to-definition, and the inlay type hints. Turn hints on. When reading unfamiliar generic code, having the inferred concrete types shown inline is the difference between decoding and reading.
src/lib.rs with only `mod` and `pub use` lines
A facade. The author has separated the public API from the implementation.
The re-export list is a curated statement of what you are meant to use, and
anything not re-exported is internal even if it is technically pub.
Read the re-exports as the crate’s table of contents. It is the highest information-per-line file in most crates.
Worked example — a procedure run
Take once_cell, which is small enough to hold in your head.
Step 1 — purpose
A cell that can be written exactly once, for lazily-initialised globals.
The problem it solves: Rust has no way to run code before main, and static
items must be const-initialised. So a global that needs runtime setup — a
compiled regex, a config, a connection pool — has nowhere to live.
Note that std has since absorbed the core of this as OnceLock and LazyLock.
Knowing that a popular crate has been partly upstreamed is exactly the kind of
ecosystem awareness that is useful in a dependency review.
Step 2 — shape
Two top-level modules: sync and unsync.
That single fact tells you most of the design, if Part 4 landed. unsync is the
single-threaded version with no atomics; sync is the thread-safe one and costs
more. It is the same Rc/Arc and RefCell/Mutex split from
4.1, applied again.
You have learned something real about the crate without reading a line of code.
Steps 3 and 4 — nouns and verbs
Two types per module: OnceCell<T> and Lazy<T>.
OnceCell<T>— empty or full;get_or_init(f)fills it if needed.Lazy<T>— anOnceCellplus the initialiser, so it derefs toTand initialises on first access.
No central trait; this is a data-structure crate rather than an abstraction
crate. That absence is itself informative — compare with serde, where the
traits are the crate.
Step 5 — one path, and what to look for
Follow get_or_init. The interesting question, given Part 2, is: how does a
method taking &self write to the cell?
The answer is interior mutability (2.6) —
UnsafeCell inside, with a synchronisation mechanism ensuring exactly one
initialisation even under concurrent access, and a // SAFETY: argument for why
handing out &T afterwards is sound.
You now know what the crate does, why it has the shape it has, where the unsafe
is, and what invariant that unsafe depends on. That is a complete-enough
understanding to depend on it, review a PR touching it, or discuss it — and it
took reading a few dozen lines.
Your exercise
Three crates, increasing difficulty. Ninety minutes each, procedure in writing.
once_cell— follow the walkthrough above, then check yourself against the source.bytes— a byte buffer with cheap cloning. The question to answer: how isBytes::clonecheap, and what does that imply about ownership?ripgrep— a large real application. Do not attempt to understand it all. Answer: what are the top-level crates in the workspace, what does each do, and where does a single search actually execute?
For each, produce four sentences: what it does, its central abstraction, one design decision the author made and why, and one thing you would ask them.
That four-sentence summary is the actual deliverable of this course. It is what “reading Rust with deep understanding” means in practice, and it is what lets you walk into a design review having formed a view.
Do exercise 1 now, before moving to Part 6. Write the four sentences here.
If you skip this, the rest of the course is theory. This is the transfer task — the point where the model either works on unfamiliar code or does not.
What you should now be able to say
- Comprehension is a grid, not a line. Start at purpose, end at syntax.
- The reading order: purpose → module tree → types → traits → one path → details.
lib.rsre-exports are the crate’s table of contents.- Tools do most of the work: docs.rs source links,
cargo tree,cargo expand, rust-analyzer inlay hints. - The deliverable is a four-sentence summary, not total understanding.