programming-rust · git:20260510.fe97ca3 · 2026-05-10 · sha256 3247df1bca27bbd5
programming-rust git:20260510.fe97ca3A
Immutable. This exact content is served forever at /api/v1/blob/3247df1bca27bbd5.
--- 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) --- ## 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. ## Ownership and data flow - 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 ## Error design - 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 ## Traits and abstraction - 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 ## Async architecture - 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 ## Module and crate layout - 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 ## Testing approach - 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) ## Ecosystem defaults - 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