The compiler loop
You build understanding of a codebase by changing it and seeing what happens. In Rust, the compiler is the first thing that happens, and it is unusually helpful. This lesson is the tight inner loop you’ll run hundreds of times: edit, ask the compiler, read what it says, fix, repeat.
What you’ll be able to do
Section titled “What you’ll be able to do”- Run
cargo checkto get a compile signal in seconds, and say how it differs from a fullcargo build. - Read a Rust diagnostic (its error code, its location, and its suggested fix) and act on it.
- Order the inner loop (check → test → fmt → clippy) and pick the smallest gate that covers a change.
- Explain why Clinker runs
cargo clippyonce without--all-targetsbefore the full pass. - Distinguish the Rust compiler’s diagnostics (about your code) from Clinker’s own run-time diagnostic codes (about a pipeline).
New terms in this lesson (each is named here before it appears below):
cargo check: type-check without building a binary; the fastest signal.- diagnostic, the compiler’s structured error report (code + location + suggested fix).
- clippy: Rust’s lint tool, run as a required gate.
-D warnings, which promotes every warning to a hard error.- exit code: the integer a run returns;
0is clean, non-zero names a failure kind.
Predict first
Section titled “Predict first”You met the {tee} typo idea informally above. Now predict the compiler’s exact
reaction before you cause it.
The compiler is your first reviewer
Section titled “The compiler is your first reviewer”Rust’s compiler doesn’t only say “no”; it explains what’s wrong and usually how to fix it. Run this, then break it on purpose:
> output appears here — press Run
It compiles and prints two lines. Now change {t} to {tee} and Run again. You
won’t get a vague crash; you’ll get the E0425 diagnostic you just predicted: a
precise location, a code you can look up, and a suggested fix. Reading these well is
the single highest-leverage skill for working in a Rust codebase.
The inner loop: check → test → fmt → clippy
Section titled “The inner loop: check → test → fmt → clippy”Clinker’s own command guide documents the gates; reach for the smallest one that covers your change and broaden as you go.
clinker ·50_TESTING_AND_COMMANDS.md doc @19acdcb4
cargo check, the fastest signal. A
cargo check
type-checks without generating a binary, so it’s much quicker than a full build. This
is your every-few-seconds command:
cargo check -p clinker-record # check one cratecargo check --workspace # check everythingcargo test, to prove behavior. Run the tests at the boundary you touched, not the
whole suite:
cargo test -p clinker-record # one crate's testscargo test -p clinker-exec value -- --exact # a targeted test by namecargo fmt --all --check, for formatting. Reports formatting drift (drop --check
to fix it).
cargo clippy, for lints. Clippy
catches what compiles but shouldn’t ship. Clinker runs it twice, and the order
matters:
cargo clippy --workspace -- -D warningscargo clippy --workspace --all-targets -- -D warningsThe first pass deliberately omits --all-targets. Why? So that code which is only
referenced from tests still trips the dead-code lint. It’s a small trick that keeps
unused production code from hiding behind the test suite, and a good first taste of
how seriously this project treats its lints:
-D warnings
makes every warning a hard error.
Worked → completion → faded
Section titled “Worked → completion → faded”Three rungs, each centered on the edit→check loop and reading the diagnostic. Run each in the Playground; the compiler is the answer key.
Worked: read a diagnostic, then fix it
Section titled “Worked: read a diagnostic, then fix it”Here is a fully worked break-and-fix. The snippet below is broken on purpose with a move error: it uses a value after handing it away. Run it, read the diagnostic (annotated for you), then read the fix.
> output appears here — press Run
cargo check refuses this with a diagnostic shaped like:
error[E0382]: borrow of moved value: `tier` --> src/main.rs:4:16 |3 | let owned = tier; | ---- value moved here4 | println!("{tier}"); | ^^^^ value borrowed here after moveRead it the way the predict-first taught: a code (E0382), two locations (where
the value moved, where you tried to use it after), and a description of the conflict.
The fix is to not use the moved-out name: print owned, or borrow instead of moving.
Here’s the borrow fix, which keeps both names usable:
let owned = &tier; // borrow, don't moveprintln!("{tier}"); // tier is still yoursprintln!("{owned}");You’ll meet move-vs-borrow properly in Data & Representation; for
now the point is the loop: edit → cargo check → read the named diagnostic → apply the fix
it points at.
Completion: finish the break, predict the code
Section titled “Completion: finish the break, predict the code”This snippet is almost a clean type error. One arm of the match is missing, so the
match isn’t exhaustive, a thing the Rust compiler refuses. Add the missing arm so it
compiles, and predict the error code the incomplete version would have produced
before you reveal it.
> output appears here — press Run
💡 Hint 1
A match in Rust must cover every variant of the enum; that’s the exhaustiveness
guarantee. Count the variants in Tier, then count the arms. The compiler names the
one you left out.
The missing arm, and the diagnostic you'd see without it
Tier::Inactive => "inactive", // the third armLeft incomplete, cargo check reports:
error[E0004]: non-exhaustive patterns: `Tier::Inactive` not coveredE0004 is the exhaustiveness error: the compiler proves at type-check time that you
handled every case. It even tells you which variant is missing, so you don’t have to
hunt. This is the same exhaustiveness that will keep your Option/Result handling
honest later in the track.
Faded: cause and read a diagnostic of your own
Section titled “Faded: cause and read a diagnostic of your own”Now with no scaffolding. Take this clean, compiling snippet and introduce one error
of your choosing: a typo in a name, a wrong type, a missing match arm, a use after
move. Run cargo check (or the Playground), read the diagnostic, then fix it back to
green.
> output appears here — press Run
What a good break-and-read looks like
Any of these breaks the build with a named diagnostic you can read and reverse:
// rename Value::Text -> Value::Txt in one place -> error[E0599] no variant `Txt`// change `v: &Value` to `v: Value` and keep `&cells` -> type-mismatch on the call// delete the Value::Text arm -> error[E0004] non-exhaustive patternsThe exercise trains a reflex, not a specific break: when cargo check prints a
diagnostic, read the code, the location, and the suggested fix before
touching anything. That reflex is the whole inner loop, and you now own it.
Clinker’s own diagnostics
Section titled “Clinker’s own diagnostics”There are two diagnostic systems, and they get conflated often. The Rust compiler checks
your code (the E04xx/E05xx codes above). Clinker, at run time, checks your
pipelines, and it has its own diagnostic codes. When a run fails with something
like E105, ask the engine to explain it:
cargo run -p clinker -- explain --code E105That explain --code is the run-time twin of the compiler’s error code: a stable
identifier you can look up. And the run itself reports outcome through its
exit code:
0 clean, and distinct non-zero codes for a config/schema error, records sent to the
DLQ, a CXL evaluation failure, or an I/O error. A script calling Clinker can branch on
those, the same way you branch on whether cargo check came back clean.
Keep the two straight: a Rust error[E0425] means your source won’t compile; a
Clinker E105 means a well-compiled engine rejected a pipeline at run time. Different
layer, different fix.
Retrieval checkpoint
Section titled “Retrieval checkpoint”💡 Hint 1
A quick way to provoke a clear diagnostic: use a value after moving it, or reference
a name that doesn’t exist (like the {tee} typo above). The compiler will name the
problem and often point at the fix.
What a good loop looks like
Edit → cargo check -p <crate> (seconds) → repeat until it compiles → cargo test -p <crate> <name> for the behavior you changed → cargo fmt --all and cargo clippy --workspace -- -D warnings before you call it done. Run the full workspace gates only
when your change crosses crate boundaries.
Connections
Section titled “Connections”You now have a feedback loop measured in seconds and, more importantly, the reflex to read a diagnostic’s code, location, and suggested fix instead of guessing. Next: how to find your way around the thirteen crates that make up the engine.
From the pipeline author’s side (optional, one-directional; Clinker’s run-time diagnostics and the DLQ, from the pipeline author’s side rather than the compiler’s):