Rust Coding Style
Apply these rules when writing or reviewing Rust code. Keep decisions consistent and terse.
Examples
- •"Pick an error strategy for this new Rust binary."
- •"Decide API shape and module layout for this Rust crate."
Guidelines
Control flow and logging
- •Never use a fallback branch unless explicitly instructed.
- •Never ignore a
matchbranch unless explicitly instructed. For unhandled events, usetracingwith at leastwarn!and include a{:?}debug of the event.
Placeholders and stubs
- •When placing placeholders or stubs, use
todo!(). Do not create empty files.
Language
- •Do not use the word "plumbing".
Error handling
- •New app: prefer
thiserror. - •IO-only code: prefer
std::io::Result. - •Existing
eyre/anyhow: stick with it. - •New library: prefer
thiserror. - •Within a codebase, keep the existing error crate unless you have a strong reason to change.
API design
- •Public API: return
Result<T, E>and avoid panics. - •Prefer newtypes over type aliases for invariants.
- •Use
impl Traitin return position for iterator APIs. - •For many optional params: use a builder.
- •Config load API: prefer free function
fn load(path: impl AsRef<Path>) -> Result<Config, Error>. - •Iterator exposure: prefer
fn iter(&self) -> impl Iterator<Item = &Item>. - •Optional config fields: use
#[serde(default)]and concrete fields; avoidOption<T>in the struct. - •Validation on construction: prefer builder with
build() -> Result<Self, Error>. - •Never implement From/TryFrom for wrapper newtypes. Unless they are Error types.
Code structure
- •Small module: use
mod.rs; large module: usemod/withmod.rs. - •Default to feature-based grouping in Rust (e.g.,
src/foo/mod.rs). - •Only use layer-based grouping for legacy or mandated layouts.
- •Use
mod.rsto curate public APIs rather than re-exporting entire trees, unless the mods are private.
Naming
- •Use singular for a thing/type, plural for a collection/module.
- •File naming:
client.rsfor a single client type,clients/for a collection.
API style: free functions vs structs
- •Prefer
struct+implwhen there is shared state, configuration, or invariants. - •Prefer struct if a function has only the struct as parameter, and tie closely to the struct's purpose.
- •Prefer free functions for stateless operations.
- •Avoid OOP-style class hierarchies; use traits for behavior and composition.
- •Use a builder when construction has multiple stages or invariant checks.
Ownership and lifetimes
- •Prefer owning types in public APIs; borrow internally where possible.
- •Avoid exposing explicit lifetimes unless they simplify the API.
Async boundaries
- •Keep async at the edges; core logic stays sync when possible.
- •Prefer explicit
async fnin public APIs; avoid boxed futures unless needed. - •Use
tokioonly when async IO or concurrency pays for the complexity.
Performance
- •Prefer
cargo criterionovercargo bench. - •Use
cargo flamegraphfor CPU hotspots. - •Use
perf+ flamegraph for low-level profiling on Linux. - •Use
samplyfor sampling + flamegraphs on macOS/Linux. - •Use
tokio-consolefor async task/latency insight. - •Use
cargo llvm-linesfor codegen size hotspots. - •Use
cargo bloatfor binary size breakdown.
Testing
- •In-file unit tests for local behavior and edge cases.
- •
tests/for integration tests across modules/crates. - •Shell integration tests for CLI workflows.
- •Smoke tests for minimal end-to-end coverage.
- •Mock tests when dependencies are expensive or flaky.
- •Fuzz tests for parsers, serializers, and input handling.
- •Use Podman Compose for containerized integration tests.