git:20260510.fe97ca3 to git:20260905.88628fb

46 added, 39 removed. Audit A to A.

---
name: programming-rust
title: "Rust Development"
description: "Idiomatic Rust architecture, ownership patterns, and ecosystem choices that survive long-term maintenance. Auto-activates in Rust projects."
license: Apache-2.0
compatibility: "Requires cargo and rustc toolchain."
capabilities: programming-rust
domains: developer
rules:
- file(Cargo.toml)
- content(rust)
---
+ ## Overview
+
+ Write Rust with explicit ownership, meaningful errors, and small APIs. Research baseline: stable Rust 1.98, checked 2026-09-05. Read `rust-toolchain.toml`, Cargo `edition`, `rust-version` (minimum supported Rust version, MSRV), features, and CI targets before choosing syntax or dependencies. Recheck official releases when updating; a newer compiler does not authorize raising the project's MSRV or changing its edition.
+
## Mental model
- Rust pushes design decisions to compile time. Most "Rust pain" comes from fighting the borrow checker after a bad architectural choice — usually shared mutable state that should have been ownership transfer, an actor, or a channel. Design data flow first; lifetimes and clones fall out cleanly when ownership is clear.
+ Make invalid states difficult to construct and make each resource's owner visible. Borrow for temporary access; own data when it must outlive the call. Choose the simplest representation that expresses the lifecycle, including a straightforward clone when independent ownership is needed.
- ## Ownership and data flow
+ ## Version-aware choices
- - Default to owned types in struct fields (`String`, `Vec<T>`, `PathBuf`) — borrowed fields force lifetime parameters that infect every caller
- - Borrow in function signatures: take `&str` / `&[T]` / `&Path`, return owned values; callers decide when to clone
- - Reach for `Cow<'a, T>` only when measurement shows allocation pressure — premature `Cow` costs more in API complexity than it saves
- - Interior mutability (`RefCell`, `Mutex`) is a signal the design has shared state — first ask whether ownership transfer or message passing fits
- - `Arc<Mutex<T>>` held across `.await` is a deadlock waiting to happen — prefer `tokio::sync::Mutex`, an actor with a channel, or splitting the state
+ - Rust 2024 requires 1.85+. Edition changes can alter temporary lifetimes and pattern behavior; follow the migration guide and review generated changes separately from feature work.
+ - Use stable standard-library facilities supported by the MSRV before adding a crate for the same small task. Nightly features remain opt-in; don't hide missing support behind unrequested compatibility branches.
+ - Rust 1.98 adds integer `format_into` with `NumBuffer`, useful for measured formatting hotspots. Its floating-point `algebraic_*` methods permit reassociation and nondeterministic results; ordinary arithmetic remains appropriate when reproducibility matters.
- ## Error design
+ ## Ownership and APIs
- - One error enum per crate boundary, derived with `thiserror`; variants describe the failure domain, not the underlying cause
- - Convert foreign errors at the boundary with `#[from]` — don't let `std::io::Error` leak through every layer
- - Applications wrap with `anyhow::Context` at the call site where context is meaningful, not at function definitions
- - Errors are part of the API — adding a variant is a breaking change; use `#[non_exhaustive]` from day one
+ - Accept `&str`, `&[T]`, and `&Path` for borrowed reads; accept owned values when the function retains or transfers them. Borrowed struct fields are useful for views with clear lifetimes, but owned fields simplify independently stored records.
+ - Keep items private until another module needs them; widen to `pub(crate)` or `pub` deliberately. Split crates for independent reuse, dependency boundaries, or deployment needs, not by default.
+ - Use enums for mutually exclusive states and newtypes for distinct domain identifiers. Keep constructors responsible for invariants; expose accessors rather than mutable fields that bypass validation.
+ - Introduce traits for an actual behavioral boundary, including a useful test seam. Choose generics for compile-time specialization and `dyn Trait` for runtime heterogeneity; neither is universally superior.
+ - Use `Rc`/`Arc` for shared ownership and a suitable synchronization primitive for mutation. Reference counting alone does not make shared mutation safe. Use `Cow` when mixed borrowing/ownership materially simplifies an API.
- ## Traits and abstraction
+ ## Errors and effects
- - Define a trait when there are two real implementations, not in anticipation; generics are easy to add, hard to remove
- - Static dispatch (`fn f<T: Trait>`) is the default; `Box<dyn Trait>` only when the type must be erased (heterogeneous collections, plugin boundaries)
- - Keep traits small and orthogonal — `Read`, `Write`, `Iterator` are the model; avoid god traits
- - Newtype pattern (`struct UserId(u64)`) for domain types — prevents mixing semantically different primitives
+ - Return `Result` for recoverable failures and `Option` for expected absence. Propagate with `?`; don't convert failed parsing, I/O, or configuration into an empty collection or default value unless that behavior is the documented contract.
+ - Use errors callers can inspect when they must recover differently. Preserve underlying causes with `Error::source`; existing `thiserror` or application context libraries can reduce boilerplate, but a new dependency is not mandatory.
+ - Include operation context without secrets. Add context where it becomes meaningful; log once at the boundary that handles the failure.
+ - Reserve `expect` for proven invariants and explain why they hold; external input is not an invariant. Use `#[non_exhaustive]` on public enums intended to grow, understanding that it requires downstream wildcard handling.
+ - Keep `unsafe` localized behind a safe interface, with explicit safety arguments covering aliasing, validity, lifetime, and synchronization. A passing compiler is not an unsafe-code proof.
- ## Async architecture
+ ## Async and concurrency
- - Pick one runtime per binary; libraries should stay runtime-agnostic where possible or feature-gate the runtime
- - Model long-lived state as actors: a task owns the state, others send messages over `mpsc`/`oneshot` — eliminates `Arc<Mutex<_>>`
- - `tokio::select!` for cancellation and multiplexing; ensure each branch is cancel-safe (no half-applied mutation across awaits)
- - CPU work belongs in `spawn_blocking` or `rayon`, never on the async runtime
- - Backpressure is the caller's responsibility — bounded channels and limited streams, not unbounded queues
+ - Keep short, non-awaiting critical sections under a synchronous mutex when contention permits. Release its guard before `.await`; use an async mutex when holding a lock across awaits is required. Actors fit asynchronously managed resources, not every shared value.
+ - Bound task concurrency and queues. Own task handles and define shutdown, cancellation, and error collection; dropping a spawned-task handle need not stop its task.
+ - In `select!`, losing branch futures owned by the expression are dropped; borrowing an externally retained future does not drop its underlying operation. Check cancellation safety and retain partial protocol state outside cancellable futures when needed.
+ - Offload blocking I/O appropriately. Bound CPU-heavy blocking work or use a CPU-oriented pool; spawning unlimited blocking jobs is not backpressure.
- ## Module and crate layout
+ ## Example
- - One concept per module; module names are nouns, not verbs (`parser`, not `parsing`)
- - Workspace from the start for anything non-trivial — split by deployment unit (binary, lib, proc-macro), not by layer
- - `pub(crate)` is the default visibility for internal items; `pub` only for the deliberate API surface
- - Re-export at the crate root what consumers need; don't force them to navigate the internal module tree
+ Reject zero and malformed values instead of silently substituting a worker count:
- ## Testing approach
+ ```rust
+ use std::num::{NonZeroUsize, ParseIntError};
- - Unit tests live in `#[cfg(test)] mod tests` next to the code they exercise
- - Integration tests in `tests/` exercise the public API as an external consumer would — they catch accidental API breakage
- - Property tests (`proptest`, `quickcheck`) for parsers, codecs, and anything with algebraic invariants
- - `insta` for snapshot tests on rendered output (errors, generated code, serialized formats)
+ fn parse_workers(input: &str) -> Result<NonZeroUsize, ParseIntError> {
+ input.parse()
+ }
+ ```
- ## Ecosystem defaults
+ ## Checklist
- - Serialization: `serde` with `serde_json` / `bincode` / `toml`
- - HTTP server: `axum` or `actix-web`; client: `reqwest`
- - Database: `sqlx` (compile-checked queries) or `diesel` (typed query builder)
- - CLI: `clap` with derive macros
- - Observability: `tracing` + `tracing-subscriber` — structured spans beat `log` for async code
+ - [ ] Syntax, dependencies, features, and targets respect the declared MSRV and edition.
+ - [ ] Ownership, error recovery, and cancellation preserve the intended contract.
+ - [ ] No unexplained clones, public surface expansion, swallowed failures, or unsafe assumptions.
+ - [ ] Use project formatting, Clippy, and focused tests when execution is authorized; cover failure paths and supported feature combinations. Report checks skipped.
+
+ ## References
+
+ - [Rust releases](https://blog.rust-lang.org/releases/) and [1.98 changes](https://blog.rust-lang.org/2026/08/20/Rust-1.98.0/).
+ - [Cargo MSRV](https://doc.rust-lang.org/cargo/reference/rust-version.html) and [Rust 2024 migration](https://doc.rust-lang.org/edition-guide/rust-2024/index.html).
+ - [Rust API guidelines](https://rust-lang.github.io/api-guidelines/future-proofing.html), [error contract](https://doc.rust-lang.org/std/error/trait.Error.html), and [NonZero](https://doc.rust-lang.org/std/num/struct.NonZero.html).
+ - [Tokio shared state](https://tokio.rs/tokio/tutorial/shared-state) and [cancellation with select](https://tokio.rs/tokio/tutorial/select).