← Driving RustL3 · The Shape of a Good Rust Codebase
Score 0
1/7 Two designs — pick one

Closed set, or open set?

ChefCheffe orders pay by card, SEPA transfer or cash. Every part of the sync must handle all three. Your agent asks: enum or trait? Choose.

the designL3·1
A · enum Payment { Card, Sepa, Cash }enum3 variantsfee: matchlabel: matchexport: matchrefund: matchB · trait PaymentMethod, 3 implstrait3 implsfee()label()export()refund()
2/7 Find the flaw

Where the error type goes

Two crates: a parser library and the CLI that uses it. Your agent picked an error type for each layer. One choice hurts the first time the CLI wants to retry. Tap it.

the designL3·2
crate: parser (library)pub fn parse(..) -> Result<Frame, anyhow::Error>? propagates every error upwardcrate: cli (binary)main() -> anyhow::Result<()>maps errors to exit codeswants: retry on TimeoutErr(..)E

Tap the part that will not survive contact with reality — or press 13.

3/7 Judge the agent

The file is always there

Your agent loads the service config at startup and reports how it handled the missing-file case. Judge it.

the designL3·3
APP_CONFIG(env var)load_config().unwrap()serve()new box, noAPP_CONFIG
The agent says

The config path comes from APP_CONFIG. I unwrap it: the file is always there in every environment we deploy to, and threading a Result through main() would just add noise.

4/7 Find the flaw

Six of eight states are lies

A Booking record for the hospitality client. Your agent modelled it with fields. Tap the part of the design that lets the data lie.

the designL3·4
struct Bookingid: BookingIdstatus: Stringcancelled: boolguest: GuestIdstates the fields can express"paid" · false"paid" · true"draft" · false"draft" · true"cancelled" · false"cancelled" · true"canceled" · true"" · false

Tap the part that will not survive contact with reality — or press 14.

5/7 Find the flaw

Three smells in one type

Your agent’s proposed core type for a single-threaded, per-request service. Three smells live in it. Tap the one that forces every caller to restructure their own code.

the designL3·5
pub struct Service<R: Repo + Clone + Send + Sync, C: Cache<R::Item>>state: Arc<Mutex<Vec<R::Item>>>pub fn get<'a>(&'a self, key: &'a str) -> Handle<'a, R::Item>caller: request handlercaller: batch jobcaller: cache warmer

Tap the part that will not survive contact with reality — or press 13.

6/7 Put it in order

The first day of a Rust codebase

Several agents will write this codebase. Put the first-day decisions in the order you want them made, then let one agent run through it.

the pipelineL3·6
day 112345
    Level 3 cleared

    What survives this level

    workspace app (binary)anyhow · exit codes · logs core (lib)enum for closed setsenum state, data per variantno unwrap past startup adapters (lib)trait for open setsnewtype ids at every edgeno lifetime in pub fn thiserror at the edge thiserror at the edge thick border = pub surface = what you review CI gate: clippy · rustfmt · MSRV pinned · dependency policy · one error strategy, decided day one
    You can now say to a coding agent“Pick one error strategy for the whole workspace before you write a module: typed errors at crate edges, anyhow only in the binary, and I review every pub item.”
    Added to your ledger
    Lifetimes as an API commitmentA lifetime in a public signature makes every caller keep something alive; buy it only when the copy is measurable.
    Traits vs enums — open vs closed extensionClosed set you control: enum. Open set others extend: trait. When unsure, enum; you can open it later.
    The newtype habitWrap ids, money and units in their own type so the compiler catches swapped arguments.
    Making illegal states unrepresentableIf two fields can disagree about the state, replace them with one enum whose variants carry their own data.
    Option and Result — absence and failure in the type systemAbsence is Option, failure is Result; both are handled where the caller can do something about it.
    Error architecture: thiserror at library edges, anyhow at application edgesTyped errors at crate edges, anyhow only in the binary; decided once, before the first module.
    unwrap / expect as a load-bearing claimunwrap is a signed claim that this cannot fail; accept it at startup, never in the request path.
    Panic vs recoverable, and who decidesRecoverable if the caller has an alternative; a panic is a decision the callee makes on everyone’s behalf.
    The public API surface is the thing to reviewpub is the only thing callers depend on and the only thing you cannot change quietly; review it line by line.
    Dependency weight, semver and MSRVEvery dependency is a maintenance contract; pin an MSRV and a dependency policy before the first cargo add.
    What the type system lets you not testWhat the types make unrepresentable you do not test; logic, ordering and IO you still do.
    One error strategy chosen up frontOne error strategy for the whole workspace, written in the brief, before any agent writes a module.
    clippy and rustfmt as enforced conventionsclippy and rustfmt run in CI from day one; conventions are enforced, not relitigated per pull request.
    You review the public surface; the rest is detailYou personally review pub items and the brief; internals are the agent’s to get wrong and fix.

    Back to the map Next: L4 The Questions Your Agent Will Ask →

    What clearing this level buys you
    “Pick one error strategy for the whole workspace before you write a module: typed errors at crate edges, anyhow only in the binary, and I review every pub item.”