Rust Coding Standards
Functional over Imperative
- Iterator chains over manual loops. map, filter, collect, try_fold, flat_map instead of for with mutable accumulators. Use for only when side effects dominate or control flow makes a chain awkward.
- Combinators on Option/Result. map, and_then, ok_or, unwrap_or_else, map_err — not match that reconstructs the same enum.
- Immutable by default. Only mut when required. Building a Vec by push? Try .collect().
- No index-based iteration. .iter(), .enumerate(), .zip(), .windows(), .chunks() over for i in 0..xs.len().
Idiomaticity
- Cheapest reference that works.
&strover&String,&[T]over&Vec<T>,&Pathover&PathBuf,impl AsRef<Path>at boundaries. impl IntoIterator<Item = T>for consuming a sequence;impl Iterator<Item = T>for returning one (avoid allocating).- Make illegal states unrepresentable. Encode invariants in the type system: enums for mutually exclusive states (not bool flags + Options), non-empty collections via Vec1 or
(T, Vec<T>), parsed types instead of validated-then-passed-as-string. If a function can't be called in some state, that state shouldn't typecheck. - Newtypes for domain values (struct UserId(u64)) over bare primitives. Use the https://crates.io/crates/nutype crate when the type needs trivial invariants enforced (non-empty, range bounds, regex, trimmed, etc.) — it generates the validating constructor and keeps the inner value unconstructable elsewhere.
- Default only when the default is meaningful.
Cow<'_, str>for sometimes-owned, sometimes-borrowed values.- Builder pattern for types with more than a couple of fields or any optional config. Owned style: pub fn with_x(mut self, x: T) -> Self, never &mut self -> &mut Self.
- Cloning discipline. .clone() is fine for Arc/Rc and small Copy-ish types; cloning a String/Vec/HashMap to dodge the borrow checker means restructure or borrow instead.
- Captured-identifier formatting. format!("{path}") over format!("{}", path).
- Display for users, Debug for developers. Don't reuse one for the other.
- Generics / impl Trait over dyn Trait. Reach for dyn only for heterogeneous collections or when monomorphization causes real code bloat.
Error Handling
- Library crates: typed error enum via thiserror.
#[derive(Debug, thiserror::Error)];#[error("...")]for Display,#[from]for From. Variants are distinct failure modes. Keep#[non_exhaustive]on public error enums. See decision 0001. - Application crates: anyhow::Result for opaque propagation. Attach .context(...) / .with_context(|| ...) at layer boundaries. User-facing CLIs use color_eyre instead of anyhow for better-formatted reports.
- unwrap()/panic! only for broken invariants. unwrap() in tests only; in production use expect("why the invariant holds"). Never for recoverable conditions.
- Never swallow errors. No let _ = result; or .ok() discards without a comment justifying it.
- Preserve the source chain. Wrap, don't replace; errors trace back via source().
- No string-typed errors crossing crate boundaries.
- Validate at the boundary, trust within.
Async & Concurrency
- No blocking in async context. No std::fs, std:🧵:sleep, blocking network, or sync Mutex held across .await — use tokio::fs, tokio::time::sleep, tokio::sync::Mutex.
- Don't reach for
Arc<Mutex<T>>reflexively. PlainArc<T>suffices ifTis immutable after construction. Spawned tasks must satisfySend + 'static— design data flow accordingly.
Logging
- Use tracing — no println!/eprintln! outside user-facing CLI output. Structured fields and spans, not interpolated strings: tracing::info!(pkg = %name, "building"), not info!("building {name}").
Conventions
- Every change: cargo clippy --all-targets -- -D warnings, cargo test. Justify any #[allow(...)] with a comment.
- unsafe requires a // SAFETY: comment covering every caller invariant.
- #[must_use] on Result-shaped returns, builders, and anything where silently dropping the value is a bug.
- #[non_exhaustive] on public enums/structs that may grow.
- Private by default. Widen to pub(crate) before pub. Curate the public API via pub use at the crate root — internal module paths shouldn't be part of the public surface.
- Testing: behavior, not implementation; one concept per test; insta for snapshot-shaped output; integration tests under tests/ for cross-crate flows.
- No dead code, commented-out code, or ownerless TODOs.
- Dependencies: prefer std, then crates already in the workspace, then crates listed on https://blessed.rs before reaching elsewhere. Versions are pinned in the workspace Cargo.toml; crate-level Cargo.tomls inherit via workspace = true, never specifying their own version.