Mental models for design

One feature may have several implementations. These models help you decide what to inspect before writing code. A model deliberately represents only part of reality; consider both its useful range and what it leaves out.

M1 — Someone owns a value and lends it for the required period

Think of ownership as responsibility for a value. A borrow grants temporary reading or mutation permission without transferring the owner’s responsibility.

In Trail, the storage layer returns a String read from a file, and the application owns the parsed Entry. Aggregation only needs to borrow &[Entry]. Work handed independently to another task often needs an owned value.

Ask: Where does this reference’s owner disappear? Do you need sharing, transfer of ownership, or an independent copy?

Try: Compare title() -> &str and title() -> String. Observe the caller’s constraints and allocations. Do not minimize clones in isolation; compare lifetime complexity as well.

M2 — Assign meaning when a value crosses a boundary

CLI strings, JSON, and SQL rows are external representations. Entry is a value with product meaning. A correct external shape and valid domain conditions require distinct checks.

In v0, Entry::new rejects zero minutes and an empty title. When you later read JSON, successful deserialization does not by itself establish validity. Convert an input DTO into a validated Entry.

Ask: Is there another entrance that can construct an invalid value? Do public fields or derived implementations bypass the conditions?

Try: Include tabs and newlines in a title and check that they are not interpreted as record separators. A later format change must preserve the same validity conditions for a record.

M3 — Decisions belong in the center; effects belong at the edge

Reason about aggregation and validation separately from network, clock, and database mechanics. The outer code gathers information, the center makes a decision, and the outer code persists or presents the result.

  1. 1CLI or HTTP input
  2. 2Parsing and validation
  3. 3Entry, totals, state transitions
  1. 1Validated result
  2. 2File or SQLite storage
  3. 3Presentation or JSON response
Consider the sequence and each role.

Ask: Does this test need a database or real time? Can you place a boundary around only the effect it requires?

Try: Test total_minutes without a file. Test persistence separately with a real file and verify the result across restart. Do not wrap everything in a trait: extract a boundary when a concrete substitution or testing need appears.

M4 — Distinguish state from permitted transitions

Updating an arbitrary job-status string can produce a job that is both completed and running again. Represent states with an enum, and allowed changes with explicit transitions.

Ask: Which state may move to which? If checking and updating are separate operations, what can change between them?

Try: Build the transition table for ex08. Then implement a database update that completes a job only while its current state is Running and its version matches the expected version. An enum can describe valid states, but storage must also prevent competing workers from violating the transition.

M5 — Async yields execution while waiting

Async is not a speed spell. While one task waits for I/O, the runtime can advance another. Adding await does not make CPU-heavy work cheaper.

Ask: Are you waiting for network, disk, or a lock, or doing computation? What do you hold across the await?

Try: Run two short timers and then two heavy computations. Compare holding a lock across an await with copying out only the necessary value before awaiting.

Reference: the Tokio tutorial.

M6 — A queue consumes finite resources too

Work accumulates when admission exceeds processing. Adding tasks does not eliminate that difference. Waiting work consumes memory, time, and connections.

Ask: What are the limits for active work and queued work? Does a full queue wait or reject? Is the waiting time bounded?

Try: Set capacity to two and workers to one in the queue visualization, then submit six items. Run the Rust bounded_queue example and distinguish rejected work from admitted work.

In a stable system where averages are meaningful, “work in the system ≈ arrival rate × time in the system” helps estimate the effect of longer waits. Do not rely on that approximation alone during sudden load or a complete stop.

Reference: Little’s queueing relationship.

M7 — Committed state and observed state differ

A database write may succeed while the response never reaches the user. A client timeout is not evidence that the operation failed to execute.

Ask: What must commit before returning success? If the response is lost and the same request returns, can you return the same result?

Try: Send the same request identifier twice. Save the record and request identifier in the same transaction, and confirm that the record count does not increase. A memory-only Set loses this guarantee after restart.

M8 — Retries have duplication and resource limits

Retrying can resolve a temporary failure. It can also repeat an already successful effect or increase the load during an outage.

Ask: Is this operation safe to perform again? Which failures are transient? What are the bounds on attempts, total duration, and waiting?

Try: Implement bounded delay in ex09 and test the first attempt, maximum attempt, and a base of zero. Consider jitter in the product, and verify waiting behavior without depending on a real clock. A retry is an explicit bounded policy, never a silent alternate implementation.

M9 — An error tells the caller what action is possible

Distinguish fixing input, retrying an operation, and asking an operator to investigate. Avoid collapsing domain failures and file/database failures into one string too early.

Ask: Must the caller classify this failure? Do responses or logs expose internal paths or complete input unnecessarily?

Try: Compare zero minutes, a missing parent directory, and a corrupted saved file. At an API boundary, map the classification to a status and retain only the necessary internal diagnostic information in logs.

M10 — Performance belongs to a measured context

Start with “where do time and memory go when aggregating ten thousand entries?” rather than “Rust is fast.” Specify which quantity should improve and which tradeoff you would accept.

Ask: Are comparison conditions identical? Are you measuring elapsed time, p95, allocations, or binary size? How many repeated measurements support the result?

Try: Measure a release build repeatedly using the same fixed fixture. Change one thing and compare correctness and memory as well as time. When the difference is small, investigate measurement noise first.

M11 — Choose an abstraction to contain an observed change

Traits are not a reason to create more files. They can contain an actual change, such as switching between two storage implementations under one contract or replacing an external clock in a core test.

Ask: Where does the next change spread? Do you need static generic selection or dynamic dyn selection? Have you actually observed a second case?

Try: Switch storage once between file and SQLite. Compare the affected code and readability with a trait versus an enum branch. Do not abstract every boundary for a possible future use. For N=1, use concrete code; extract reuse after N≥2 or a demonstrated problem.

M12 — Cleanup, cancellation, and shutdown are designed paths

RAII connects cleanup to scope exit, but does not automatically undo the meaning of persisted work. When a future is dropped, consider which effects have already happened.

Ask: What stops and what waits during interruption? Who recovers an incomplete write or a job left Running?

Try: On shutdown, close admission and wait for active work until a deadline. After forced termination, reclaim work only after the lease expires, and test that two completions cannot both commit.

Reference: Tokio graceful shutdown.

A model for reading unsafe code

List what the implementation must establish for its safe external contract: pointer validity, initialization, lifetime, aliasing, and use across threads. Passing tests alone does not prove those requirements.

Adding unsafe is not a mandatory product exercise. Investigate it in an isolated experiment only after measurement demonstrates a need. Read the Rustonomicon with the proof obligations in view.

2024–2026 mental-model update: editions and agents add review boundaries

The 2024 project goals put ergonomic async work and the 2024 Edition on the roadmap. Rust 1.85.0 stabilized that edition in 2025, including explicit unsafe expectations and changed temporary/lifetime-related rules. This reinforces M1, M5, and the unsafe model: edition migration is an explicit boundary, not proof that a lifetime or concurrency design is sound. Keep Cargo.toml's edition, rust-version, and toolchain in the design record; run cargo fix conservatively and review behavior rather than accepting its output as a semantic recommendation.

The current official blog lists Rust 1.99.0 on 2026-10-01 and September security and maintainer posts. A current version number does not replace a supply-chain or recovery model. For M7–M9, add an exercise that pins one dependency, changes only its allowed version range in a branch, and proves that a failed update leaves the durable Trail data unchanged. For M10, measure one release/toolchain change with identical fixtures before attributing a performance difference to Rust or AI.

Google's Argon announcement reports that large Rust migrations use auditing, emulation, review, compiler-output study, and profile-guided experiments. That turns into a practical AI boundary: generated code is an external input at M2, and a generated patch crosses M3 only after tests and human review. The r/rust job and AI-post threads contain diverging personal views; label them community experience. Do not infer hiring trends, agent reliability, or safety from them.

MENTAL MODEL / REASONING ORDER

From an announcement to your own decision.

Primary sources

Compare the announcement with the conditions in the paper and official documentation.

SOURCES

01
The Rust Programming Language ↗doc.rust-lang.org · unknown
02
Rust Project goals for 2024 ↗blog.rust-lang.org · 2024-08-12
03
Announcing Rust 1.85.0 and Rust 2024 ↗blog.rust-lang.org · 2025-02-20
04
Announcing Rust 1.99.0 ↗blog.rust-lang.org · 2026-10-01
05
Introducing Gemini 4 Argon ↗blog.google · 2026-10-01
06
r/rust Google migration discussion (community experience) ↗www.reddit.com · 2026-10-01
07
r/rust AI-post discussion (community experience) ↗www.reddit.com · 2026-09-12

YOUR NOTES