Rust Maintainability

Rust maintainability guidance applies the repos software-change preferences to Rust code, APIs, modules, errors, tests, dependencies, documentation, and release risk. It favors Rust that is easy to read locally, hard to misuse, and honest about public behavior.

Core Preference

Prefer reader-first Rust that still respects ownership, performance, and API compatibility. A Rust change is better when it reduces the number of concepts, fields, lifetimes, jumps, and hidden invariants a maintainer must hold at once.

Use Reader Locality when extraction, module movement, or helper placement affects how much context a reader must reconstruct. Use Code Shape when the change is primarily about live context, extraction, structure/behavior separation, reversible structure, cohesion, coupling, naming, or local expression shape.

Rust Review Checklist

Use this checklist to review Rust changes without flattening every preference into a separate reviewed rule file. Some checklist items already have durable rule pages in Rust API And Crate Shape Rules; other items are narrower prompts that should stay here until they prove reusable enough to promote.

Core Maintainability

Public API Shape

Crate Roots, Modules, And Re-exports

Rustdoc And Written API Contracts

Tests And Evidence

Performance And Async Behavior

CI, Lints, And Unsafe Code

Feature Flags And Dependency Policy

Workspace And Crate Boundaries

Imports, Ordering, And Types

Local Style And Documentation Hygiene

Release Checks

Review Practice

Expression Shape

Concepts and Types

Extract a type or helper when it names a real concept and reduces downstream reasoning. Avoid wrapper types, tuple structs, or parameter bags that are constructed only to be immediately destructured.

Prefer types that make invalid states hard to express when the invariant is repeated, meaningful, and useful across a boundary. Keep a guard clause or local validation when the rule is one-off and a new type would add ceremony without reducing risk.

When a design explanation repeatedly uses a domain noun that has no source name, test whether a concept is missing. Extract only when the new local, function, enum, type, or module owns policy, protects an invariant, or removes details the reader can safely forget. Temporary nouns in a linear parser or transformation do not require durable types.

Prefer concrete structs and enums before traits when the concept is still local. Introduce traits, generic layers, callbacks, or framework-shaped abstractions only after a real variation point earns the extra concept a reader must understand.

Use Test Observable Behavior before trusting a refactor that moves concepts around.

Functions and Control Flow

Keep functions small enough to fit in a maintainer's head. Longer functions are acceptable when their visual paragraphs map directly to meaningful phases.

Prefer named locals for important calls, side-effectful operations, and values whose role matters to the reader. Use iterators for pure transformations and explicit loops for mutation, control flow, or important side effects.

Do not compress navigation, parsing, rendering, or stateful control flow into dense iterator chains when named locals would make edge cases easier to audit.

Use Separate Structure From Behavior when a rename, move, extraction, or formatting pass can be reviewed independently from a behavior change.

Modules and Visibility

Modules should represent concepts, not arbitrary file-size cuts. Put the central type, trait, or workflow near the top, keep inherent impls close to their type, and make re-exports deliberate.

For a substantial structure review, scan the declaration outline before every implementation and inventory behavior separately. Source Coherence Review maps behaviors to owners and declarations back to the behavior that justifies them. It also distinguishes implementation length from focused docs and colocated tests.

Arrange modules in the order a reader needs the concepts: public or central types first, core methods near their type, helpers near their callers, and tests near the behavior they prove. Split a file when it lowers the number of live facts a reader must hold, not merely because a file crossed a line-count threshold.

Use the smallest visibility that protects real invariants and keeps refactors cheap. Avoid visibility ceremony that only makes the code look more layered.

Re-exports should improve discovery and preserve a clear owner. Do not use a facade module to hide which module owns a concept or to make accidental internals feel stable.

API Boundaries

Public APIs should be predictable, narrow, and hard to misuse. Prefer standard conversion traits over ad hoc conversion names when the standard trait expresses the relationship. Keep dependencies out of public types unless the dependency is part of the abstraction.

Rustdoc is part of the public API. Public modules, types, fields, functions, errors, feature flags, and examples should document caller-facing contracts: what the item represents, who owns or mutates state, what invariants hold, what can fail, and what compatibility the caller may rely on.

Public modules should answer what concept they own, the main workflow, related modules, and what they intentionally do not own.

Public types should explain who should construct them, what invariants they preserve, lifecycle or ownership behavior, concurrency expectations, and how they relate to neighboring types.

Public fields should document meaning, valid values, default behavior, invariants, and interactions with other fields.

Public functions should document caller-facing behavior, state changes, side effects, and allocation, blocking, I/O, global registration, or background work when relevant.

Use From, TryFrom, Default, Display, and common derives when they make value types easier and safer to use without weakening invariants.

APIs that touch process-wide, runtime-wide, terminal, filesystem, network, global registration, background-task, or UI state need explicit lifecycle documentation. Call out startup, shutdown, cleanup, drop behavior, cancellation, ordering, retry expectations, and whether multiple instances may coexist when those details affect callers.

Fallible APIs should explain what can fail, whether retrying is useful, whether partial state may have changed, and how callers recover.

Async or concurrent APIs should document runtime assumptions, cancellation, backpressure, capacity, ordering, cloneability, and producer/consumer lifetime behavior.

Feature-gated APIs should name the feature and explain what it enables: public API, runtime behavior, dependencies, examples, platform support, or docs.rs coverage. A feature flag is a public integration surface, not only a build toggle.

Use Conventional Commits only when the repo already follows that spec; otherwise keep jj descriptions and commits in the canonical Chris Beams and Tim Pope style from Commit Messages For History.

Errors

Rust error types should preserve enough context for callers to act. Application boundaries may use broad error types; reusable libraries should expose intentional errors with stable kinds and recoverable context.

Handle fallible side effects near the boundary or surface them to the caller. Do not swallow failures unless the ignored failure is intentional and obvious.

Keep partial-state updates conservative. Preserve the current usable state when refresh, parsing, I/O, or rendering preparation fails and the previous state is still valid.

Use Preserve Error Context when wrapping or mapping errors. Use Write Actionable Error Messages when the visible message needs to help a user, operator, caller, or support person make progress.

Testing

Tests should protect observable behavior rather than private implementation shape. Prefer focused unit tests, golden tests, round trips, property tests, or integration checks based on the changed surface and likely failure.

New public API slices should usually include a practical example or doctest. Use examples to prove ownership, lifecycle, errors, feature flags, or integration shape; avoid examples that only show a constructor can be called.

For UI, terminal, protocol, parser, formatter, or state-machine behavior, pick evidence that matches the claim. Snapshot tests, rendered fixtures, byte-level assertions, deterministic integration tests, manual demos, screenshots, or generated reference projects can all be appropriate when they prove the changed surface.

Review snapshot updates before accepting them. A snapshot change should represent intentional behavior, not incidental formatting churn.

For scrollable or terminal UI, prefer stable dimensions and saturating arithmetic. Calculations involving terminal height, line count, selected row, or scroll offset should handle empty content and very small viewports.

When interactive behavior lacks automated coverage, document the manual verification that exercised it.

Use Smallest Trustworthy Verification to pick the cheapest credible check and Report Verification Honestly in the handoff.

Performance

Prefer clarity first, then measure. Do not add caching, concurrency, allocation tricks, or unsafe code from a guess alone. Keep hot-path changes small enough to benchmark directly, and document intentional performance-sensitive shapes where future maintainers will see them.

Unsafe code should have a narrow purpose, a nearby safety argument, and tests or review evidence that protect the invariant.

Dependency Changes

Prefer the widest honest semver-compatible requirement that preserves the crate's intended behavior and downstream integration shape. Use lockfile updates for newer compatible releases, and raise Cargo.toml minimums only when a newer minimum is actually required.

Keep maintenance-only dependency updates separate from changes that may alter parsing, trait behavior, MSRV, feature behavior, or public API semantics.

Agent Snippet

For copyable AGENTS.md guidance, use Rust Agent Instructions.

Review Questions

References

Source Use Note
C-VALIDATE adapts Prefer argument types that rule out bad inputs.
C-CONV-TRAITS adapts Standard conversion traits keep APIs interoperable.
C-GOOD-ERR adapts Error types should be meaningful and well-behaved.
M-SIMPLE-ABSTRACTIONS adapts Public APIs should avoid exposing nested generic machinery.
Ed Page Rust Style adapts Reader-locality and Rust code-shape guidance.