rust-core · diff

v2.0.0 to v2.0.0

122 added, 327 removed. Audit A to A.

---
name: rust-core
description: >
Use when writing, reviewing, or refactoring Rust code. Covers ownership,
borrowing, error handling, async patterns, API design, memory optimization,
performance, testing, and common anti-patterns. Targets Rust 1.80+ and
Edition 2024. Invoke with /rust-core.
globs: ["**/*.rs"]
license: MIT
metadata:
author: mte90
version: "2.0.0"
tags:
- rust
- core
- ownership
- error-handling
- async
- performance
- testing
sources:
- Rust API Guidelines
- Rust Performance Book
- Rust Design Patterns
- ripgrep, tokio, serde, polars codebases
---
- # Rust Core
+ > **Objective:** Use this skill to reduce borrow-checker rejections, unwrap panics in production, and data-race incidents by enforcing ownership discipline, error-handling patterns, and concurrency rules below.
- Comprehensive guide for writing high-quality, idiomatic, and optimized Rust code. Targets Rust 1.80+ and Edition 2024.
+ # Rust Core
- ## When to Apply
+ Guide for writing high-quality, idiomatic, and optimized Rust code. Targets Rust 1.80+ and Edition 2024.
- - Writing new Rust functions, structs, or modules
- - Implementing error handling or async code
- - Designing public APIs for libraries
- - Reviewing code for ownership/borrowing issues
- - Optimizing memory usage or reducing allocations
- - Tuning performance for hot paths
- - Refactoring existing Rust code
- - Fixing compiler errors (see [references/compiler-errors.md](references/compiler-errors.md))
+ See `references/when-to-apply.md` for usage context.
## Edition 2024 Changes (Rust 1.85+)
| Change | Impact | Action |
|--------|--------|--------|
| `unsafe` blocks required for `extern` blocks | `extern "C" { ... }` now needs `unsafe { extern "C" { ... } }` | Wrap extern blocks in `unsafe` |
| `unsafe_op_in_unsafe_fn` lint | `unsafe fn` body requires explicit `unsafe` blocks for unsafe ops | Add `unsafe { ... }` inside unsafe fns |
| `if let` temporary scope | Temporaries dropped at end of `if let` block | May extend borrow lifetimes |
| `gen` keyword reserved | `gen { ... }` blocks for iterators | Use `gen` for lazy iterators |
| `#[diagnostic::on_unimplemented]` | Better error messages for trait failures | Add to library traits |
| Nested or-patterns | `Some(1 \| 2)` stabilized | Use in match arms |
Run `cargo fix --edition` to apply most changes automatically.
## Rule Categories by Priority
| Priority | Category | Prefix | Rules |
|----------|----------|--------|-------|
| 1 | Ownership & Borrowing | `own-` | 12 |
| 2 | Error Handling | `err-` | 12 |
| 3 | Memory Optimization | `mem-` | 15 |
| 4 | API Design | `api-` | 15 |
| 5 | Async/Await | `async-` | 15 |
| 6 | Compiler Optimization | `opt-` | 12 |
| 7 | Naming Conventions | `name-` | 16 |
| 8 | Type Safety | `type-` | 10 |
| 9 | Testing | `test-` | 13 |
| 10 | Documentation | `doc-` | 11 |
| 11 | Performance Patterns | `perf-` | 11 |
| 12 | Project Structure | `proj-` | 11 |
| 13 | Clippy & Linting | `lint-` | 11 |
| 14 | Anti-patterns | `anti-` | 15 |
Detailed rules live in `rules/` — 14 files, one per prefix group (`ownership.md` covers `own-`, `error-handling.md` covers `err-`, …). Each `## <rule-id>` section inside a category file is one rule, so a grep for a rule id lands directly on it.
- ## Rule Index
+ See `references/rule-index.md` for the full 179-rule catalog.
- ### Ownership & Borrowing (`own-`)
+ ## Ownership & Borrowing
- | Rule | Description |
- |------|-------------|
- | `own-borrow-over-clone` | Prefer `&T` borrowing over `.clone()` |
- | `own-slice-over-vec` | Accept `&[T]` not `&Vec<T>`, `&str` not `&String` |
- | `own-cow-conditional` | Use `Cow<'a, T>` for conditional ownership |
- | `own-arc-shared` | Use `Arc<T>` for thread-safe shared ownership |
- | `own-rc-single-thread` | Use `Rc<T>` for single-threaded sharing |
- | `own-refcell-interior` | Use `RefCell<T>` for interior mutability (single-thread) |
- | `own-mutex-interior` | Use `Mutex<T>` for interior mutability (multi-thread) |
- | `own-rwlock-readers` | Use `RwLock<T>` when reads dominate writes |
- | `own-copy-small` | Derive `Copy` for small, trivial types |
- | `own-clone-explicit` | Make `Clone` explicit, avoid implicit copies |
- | `own-move-large` | Move large data instead of cloning |
- | `own-lifetime-elision` | Rely on lifetime elision when possible |
+ **Prefer `&T` borrowing over `.clone()`** because cloning allocates and copies memory unnecessarily when a reference suffices, wasting CPU and memory in hot paths.
- ### Error Handling (`err-`)
+ **Accept `&[T]` not `&Vec<T>`, `&str` not `&String`** because slice/string references accept any borrowed sequence (including subslices), making functions more flexible without sacrificing safety.
- | Rule | Description |
- |------|-------------|
- | `err-thiserror-lib` | Use `thiserror` for library error types |
- | `err-anyhow-app` | Use `anyhow` for application error handling |
- | `err-result-over-panic` | Return `Result`, don't panic on expected errors |
- | `err-context-chain` | Add context with `.context()` or `.with_context()` |
- | `err-no-unwrap-prod` | Never use `.unwrap()` in production code |
- | `err-expect-bugs-only` | Use `.expect()` only for programming errors |
- | `err-question-mark` | Use `?` operator for clean propagation |
- | `err-from-impl` | Use `#[from]` for automatic error conversion |
- | `err-source-chain` | Use `#[source]` to chain underlying errors |
- | `err-lowercase-msg` | Error messages: lowercase, no trailing punctuation |
- | `err-doc-errors` | Document errors with `# Errors` section |
- | `err-custom-type` | Create custom error types, not `Box<dyn Error>` |
+ **Use `Cow<'a, T>` for conditional ownership** because some callers can provide borrowed data while others need owned data, avoiding unnecessary clones in the common case.
- ### Memory Optimization (`mem-`)
+ **Use `Arc<T>` for thread-safe shared ownership, `Rc<T>` for single-threaded** because reference counting enables multiple owners without data races (`Arc` uses atomic ops, `Rc` does not).
- | Rule | Description |
- |------|-------------|
- | `mem-with-capacity` | Use `with_capacity()` when size is known |
- | `mem-smallvec` | Use `SmallVec` for usually-small collections |
- | `mem-arrayvec` | Use `ArrayVec` for bounded-size collections |
- | `mem-box-large-variant` | Box large enum variants to reduce type size |
- | `mem-boxed-slice` | Use `Box<[T]>` instead of `Vec<T>` when fixed |
- | `mem-thinvec` | Use `ThinVec` for often-empty vectors |
- | `mem-clone-from` | Use `clone_from()` to reuse allocations |
- | `mem-reuse-collections` | Reuse collections with `clear()` in loops |
- | `mem-avoid-format` | Avoid `format!()` when string literals work |
- | `mem-write-over-format` | Use `write!()` instead of `format!()` |
- | `mem-arena-allocator` | Use arena allocators for batch allocations |
- | `mem-zero-copy` | Use zero-copy patterns with slices and `Bytes` |
- | `mem-compact-string` | Use `CompactString` for small string optimization |
- | `mem-smaller-integers` | Use smallest integer type that fits |
- | `mem-assert-type-size` | Assert hot type sizes to prevent regressions |
+ **Use `RefCell<T>` (single-thread) or `Mutex<T>`/`RwLock<T>` (multi-thread) for interior mutability** because these types enforce borrowing rules at runtime when compile-time checks cannot verify them.
- ### API Design (`api-`)
+ **Derive `Copy` for small, trivial types** because copy types avoid heap allocation and indirection, but only for types fitting in a few registers (typically ≤16 bytes).
- | Rule | Description |
- |------|-------------|
- | `api-builder-pattern` | Use Builder pattern for complex construction |
- | `api-builder-must-use` | Add `#[must_use]` to builder types |
- | `api-newtype-safety` | Use newtypes for type-safe distinctions |
- | `api-typestate` | Use typestate for compile-time state machines |
- | `api-sealed-trait` | Seal traits to prevent external implementations |
- | `api-extension-trait` | Use extension traits to add methods to foreign types |
- | `api-parse-dont-validate` | Parse into validated types at boundaries |
- | `api-impl-into` | Accept `impl Into<T>` for flexible string inputs |
- | `api-impl-asref` | Accept `impl AsRef<T>` for borrowed inputs |
- | `api-must-use` | Add `#[must_use]` to `Result`-returning functions |
- | `api-non-exhaustive` | Use `#[non_exhaustive]` for future-proof enums/structs |
- | `api-from-not-into` | Implement `From`, not `Into` (auto-derived) |
- | `api-default-impl` | Implement `Default` for sensible defaults |
- | `api-common-traits` | Implement `Debug`, `Clone`, `PartialEq` eagerly |
- | `api-serde-optional` | Gate `Serialize`/`Deserialize` behind feature flag |
+ **Move large data instead of cloning** because moving transfers ownership with zero cost, while cloning always allocates and copies.
- ### Async/Await (`async-`)
+ **Rely on lifetime elision when possible** because explicit lifetimes add verbosity; the compiler infers common patterns correctly in most cases.
- | Rule | Description |
- |------|-------------|
- | `async-tokio-runtime` | Use Tokio for production async runtime |
- | `async-no-lock-await` | Never hold `Mutex`/`RwLock` across `.await` |
- | `async-spawn-blocking` | Use `spawn_blocking` for CPU-intensive work |
- | `async-tokio-fs` | Use `tokio::fs` not `std::fs` in async code |
- | `async-cancellation-token` | Use `CancellationToken` for graceful shutdown |
- | `async-join-parallel` | Use `tokio::join!` for parallel operations |
- | `async-try-join` | Use `tokio::try_join!` for fallible parallel ops |
- | `async-select-racing` | Use `tokio::select!` for racing/timeouts |
- | `async-bounded-channel` | Use bounded channels for backpressure |
- | `async-mpsc-queue` | Use `mpsc` for work queues |
- | `async-broadcast-pubsub` | Use `broadcast` for pub/sub patterns |
- | `async-watch-latest` | Use `watch` for latest-value sharing |
- | `async-oneshot-response` | Use `oneshot` for request/response |
- | `async-joinset-structured` | Use `JoinSet` for dynamic task groups |
- | `async-clone-before-await` | Clone data before await, release locks |
+ See `rules/ownership.md` for detailed rules with failure modes and examples.
- ### Compiler Optimization (`opt-`)
+ ## Error Handling
- | Rule | Description |
- |------|-------------|
- | `opt-inline-small` | Use `#[inline]` for small hot functions |
- | `opt-inline-always-rare` | Use `#[inline(always)]` sparingly |
- | `opt-inline-never-cold` | Use `#[inline(never)]` for cold paths |
- | `opt-cold-unlikely` | Use `#[cold]` for error/unlikely paths |
- | `opt-likely-hint` | Use `likely()`/`unlikely()` for branch hints |
- | `opt-lto-release` | Enable LTO in release builds |
- | `opt-codegen-units` | Use `codegen-units = 1` for max optimization |
- | `opt-pgo-profile` | Use PGO for production builds |
- | `opt-target-cpu` | Set `target-cpu=native` for local builds |
- | `opt-bounds-check` | Use iterators to avoid bounds checks |
- | `opt-simd-portable` | Use portable SIMD for data-parallel ops |
- | `opt-cache-friendly` | Design cache-friendly data layouts (SoA) |
+ **Use `thiserror` for library error types, `anyhow` for application error handling** because libraries need stable, documented error types (thiserror), while applications prioritize ergonomic propagation (anyhow).
- ### Naming Conventions (`name-`)
+ **Return `Result`, don't panic on expected errors** because panics crash the entire program while `Result` lets callers recover or propagate gracefully.
- | Rule | Description |
- |------|-------------|
- | `name-types-camel` | Use `UpperCamelCase` for types, traits, enums |
- | `name-variants-camel` | Use `UpperCamelCase` for enum variants |
- | `name-funcs-snake` | Use `snake_case` for functions, methods, modules |
- | `name-consts-screaming` | Use `SCREAMING_SNAKE_CASE` for constants/statics |
- | `name-lifetime-short` | Use short lowercase lifetimes: `'a`, `'de`, `'src` |
- | `name-type-param-single` | Use single uppercase for type params: `T`, `E`, `K`, `V` |
- | `name-as-free` | `as_` prefix: free reference conversion |
- | `name-to-expensive` | `to_` prefix: expensive conversion |
- | `name-into-ownership` | `into_` prefix: ownership transfer |
- | `name-no-get-prefix` | No `get_` prefix for simple getters |
- | `name-is-has-bool` | Use `is_`, `has_`, `can_` for boolean methods |
- | `name-iter-convention` | Use `iter`/`iter_mut`/`into_iter` for iterators |
- | `name-iter-method` | Name iterator methods consistently |
- | `name-iter-type-match` | Iterator type names match method |
- | `name-acronym-word` | Treat acronyms as words: `Uuid` not `UUID` |
- | `name-crate-no-rs` | Crate names: no `-rs` suffix |
+ **Add context with `.context()` or `.with_context()`** because error chains show what operation failed and why, making debugging production incidents feasible.
- ### Type Safety (`type-`)
+ **Never use `.unwrap()` in production code** because `.unwrap()` panics when the value is `None` or `Err`, turning recoverable errors into crashes.
- | Rule | Description |
- |------|-------------|
- | `type-newtype-ids` | Wrap IDs in newtypes: `UserId(u64)` |
- | `type-newtype-validated` | Newtypes for validated data: `Email`, `Url` |
- | `type-enum-states` | Use enums for mutually exclusive states |
- | `type-option-nullable` | Use `Option<T>` for nullable values |
- | `type-result-fallible` | Use `Result<T, E>` for fallible operations |
- | `type-phantom-marker` | Use `PhantomData<T>` for type-level markers |
- | `type-never-diverge` | Use `!` type for functions that never return |
- | `type-generic-bounds` | Add trait bounds only where needed |
- | `type-no-stringly` | Avoid stringly-typed APIs, use enums/newtypes |
- | `type-repr-transparent` | Use `#[repr(transparent)]` for FFI newtypes |
+ **Use `.expect()` only for programming errors** because `.expect("invariant violated")` documents invariants that should never fail if the code is correct.
- ### Testing (`test-`)
+ **Use `?` operator for clean propagation** because `?` propagates errors without boilerplate, keeping error paths readable.
- | Rule | Description |
- |------|-------------|
- | `test-cfg-test-module` | Use `#[cfg(test)] mod tests { }` |
- | `test-use-super` | Use `use super::*;` in test modules |
- | `test-integration-dir` | Put integration tests in `tests/` directory |
- | `test-descriptive-names` | Use descriptive test names |
- | `test-arrange-act-assert` | Structure tests as arrange/act/assert |
- | `test-proptest-properties` | Use `proptest` for property-based testing |
- | `test-mockall-mocking` | Use `mockall` for trait mocking |
- | `test-mock-traits` | Use traits for dependencies to enable mocking |
- | `test-fixture-raii` | Use RAII pattern (Drop) for test cleanup |
- | `test-tokio-async` | Use `#[tokio::test]` for async tests |
- | `test-should-panic` | Use `#[should_panic]` for panic tests |
- | `test-criterion-bench` | Use `criterion` for benchmarking |
- | `test-doctest-examples` | Keep doc examples as executable tests |
- | `test-mutation-assertions` | Use mutation testing to verify test assertions |
- | `test-mutation-trustworthiness` | Make mutation runs trustworthy and affordable |
+ **Use `#[from]` for automatic error conversion** because `#[from]` generates `From` implementations, reducing glue code for common error types.
- ### Documentation (`doc-`)
+ **Use `#[source]` to chain underlying errors** because `#[source]` preserves the error chain for debugging, enabling `error.source()` traversal.
- | Rule | Description |
- |------|-------------|
- | `doc-all-public` | Document all public items with `///` |
- | `doc-module-inner` | Use `//!` for module-level documentation |
- | `doc-examples-section` | Include `# Examples` with runnable code |
- | `doc-errors-section` | Include `# Errors` for fallible functions |
- | `doc-panics-section` | Include `# Panics` for panicking functions |
- | `doc-safety-section` | Include `# Safety` for unsafe functions |
- | `doc-question-mark` | Use `?` in examples, not `.unwrap()` |
- | `doc-hidden-setup` | Use `# ` prefix to hide example setup code |
- | `doc-intra-links` | Use intra-doc links: `[Vec]` |
- | `doc-link-types` | Link related types and functions in docs |
- | `doc-cargo-metadata` | Fill `Cargo.toml` metadata |
+ **Error messages: lowercase, no trailing punctuation** because Rust convention uses lowercase for error messages (like `std`), and punctuation adds noise.
- ### Performance Patterns (`perf-`)
+ **Document errors with `# Errors` section** because users need to know what failures to expect and how to handle them.
- | Rule | Description |
- |------|-------------|
- | `perf-iter-over-index` | Prefer iterators over manual indexing |
- | `perf-iter-lazy` | Keep iterators lazy, `collect()` only when needed |
- | `perf-collect-once` | Don't `collect()` intermediate iterators |
- | `perf-entry-api` | Use `entry()` API for map insert-or-update |
- | `perf-drain-reuse` | Use `drain()` to reuse allocations |
- | `perf-extend-batch` | Use `extend()` for batch insertions |
- | `perf-chain-avoid` | Avoid `chain()` in hot loops |
- | `perf-collect-into` | Use `collect_into()` for reusing containers |
- | `perf-black-box-bench` | Use `black_box()` in benchmarks |
- | `perf-release-profile` | Optimize release profile settings |
- | `perf-profile-first` | Profile before optimizing |
+ **Create custom error types, not `Box<dyn Error>`** because custom types enable pattern matching, better diagnostics, and stable APIs.
- ### Project Structure (`proj-`)
+ See `rules/error-handling.md` for detailed rules with failure modes and examples.
- | Rule | Description |
- |------|-------------|
- | `proj-lib-main-split` | Keep `main.rs` minimal, logic in `lib.rs` |
- | `proj-mod-by-feature` | Organize modules by feature, not type |
- | `proj-flat-small` | Keep small projects flat |
- | `proj-mod-rs-dir` | Use `mod.rs` for multi-file modules |
- | `proj-pub-crate-internal` | Use `pub(crate)` for internal APIs |
- | `proj-pub-super-parent` | Use `pub(super)` for parent-only visibility |
- | `proj-pub-use-reexport` | Use `pub use` for clean public API |
- | `proj-prelude-module` | Create `prelude` module for common imports |
- | `proj-bin-dir` | Put multiple binaries in `src/bin/` |
- | `proj-workspace-large` | Use workspaces for large projects |
- | `proj-workspace-deps` | Use workspace dependency inheritance |
+ ## Memory Optimization
- ### Clippy & Linting (`lint-`)
+ **Use `with_capacity()` when size is known** because pre-allocating avoids repeated reallocations as the collection grows.
- | Rule | Description |
- |------|-------------|
- | `lint-deny-correctness` | `#![deny(clippy::correctness)]` |
- | `lint-warn-suspicious` | `#![warn(clippy::suspicious)]` |
- | `lint-warn-style` | `#![warn(clippy::style)]` |
- | `lint-warn-complexity` | `#![warn(clippy::complexity)]` |
- | `lint-warn-perf` | `#![warn(clippy::perf)]` |
- | `lint-pedantic-selective` | Enable `clippy::pedantic` selectively |
- | `lint-missing-docs` | `#![warn(missing_docs)]` |
- | `lint-unsafe-doc` | `#![warn(clippy::undocumented_unsafe_blocks)]` |
- | `lint-cargo-metadata` | `#![warn(clippy::cargo)]` for published crates |
- | `lint-rustfmt-check` | Run `cargo fmt --check` in CI |
- | `lint-workspace-lints` | Configure lints at workspace level |
+ **Use `SmallVec` for usually-small collections, `ArrayVec` for bounded-size** because stack allocation avoids heap overhead for small sizes, with fallback to heap when needed.
- ### Anti-patterns (`anti-`)
+ **Box large enum variants to reduce type size** because boxing the largest variant makes the enum size predictable and often smaller overall.
- | Rule | Description |
- |------|-------------|
- | `anti-unwrap-abuse` | Don't use `.unwrap()` in production code |
- | `anti-expect-lazy` | Don't use `.expect()` for recoverable errors |
- | `anti-clone-excessive` | Don't clone when borrowing works |
- | `anti-lock-across-await` | Don't hold locks across `.await` |
- | `anti-string-for-str` | Don't accept `&String` when `&str` works |
- | `anti-vec-for-slice` | Don't accept `&Vec<T>` when `&[T]` works |
- | `anti-index-over-iter` | Don't use indexing when iterators work |
- | `anti-panic-expected` | Don't panic on expected/recoverable errors |
- | `anti-empty-catch` | Don't use empty `if let Err(_) = ...` blocks |
- | `anti-over-abstraction` | Don't over-abstract with excessive generics |
- | `anti-premature-optimize` | Don't optimize before profiling |
- | `anti-type-erasure` | Don't use `Box<dyn Trait>` when `impl Trait` works |
- | `anti-format-hot-path` | Don't use `format!()` in hot paths |
- | `anti-collect-intermediate` | Don't `collect()` intermediate iterators |
- | `anti-stringly-typed` | Don't use strings for structured data |
+ **Use `Box<[T]>` instead of `Vec<T>` when fixed size** because `Box<[T]>` cannot grow/shrink, communicating intent and avoiding capacity overhead.
- ## Ownership & Borrowing
+ **Use `clone_from()` to reuse allocations** because `clone_from()` can reuse existing buffers instead of allocating fresh memory.
- - Prefer `&T` borrowing over `.clone()`
- - Accept `&[T]` not `&Vec<T>`, `&str` not `&String`
- - Use `Cow<'a, T>` for conditional ownership
- - Use `Arc<T>` for thread-safe shared ownership, `Rc<T>` for single-threaded
- - Use `RefCell<T>` (single-thread) or `Mutex<T>`/`RwLock<T>` (multi-thread) for interior mutability
- - Derive `Copy` for small, trivial types
- - Move large data instead of cloning
- - Rely on lifetime elision when possible
+ **Reuse collections with `clear()` in loops** because `clear()` keeps capacity, avoiding repeated allocations in hot loops.
- ## Error Handling
+ **Avoid `format!()` when string literals work; use `write!()` instead** because `format!()` always allocates, while `write!()` can write to existing buffers.
- - Use `thiserror` for library error types, `anyhow` for application error handling
- - Return `Result`, don't panic on expected errors
- - Add context with `.context()` or `.with_context()`
- - Never use `.unwrap()` in production code
- - Use `.expect()` only for programming errors
- - Use `?` operator for clean propagation
- - Use `#[from]` for automatic error conversion
- - Use `#[source]` to chain underlying errors
- - Error messages: lowercase, no trailing punctuation
- - Document errors with `# Errors` section
- - Create custom error types, not `Box<dyn Error>`
+ **Use smallest integer type that fits** because smaller types reduce memory footprint and can improve cache locality.
- ## Memory Optimization
+ **Assert hot type sizes to prevent regressions** because `assert!(std::mem::size_of::<T>() <= N)` catches accidental bloat in performance-critical types.
- - Use `with_capacity()` when size is known
- - Use `SmallVec` for usually-small collections, `ArrayVec` for bounded-size
- - Box large enum variants to reduce type size
- - Use `Box<[T]>` instead of `Vec<T>` when fixed size
- - Use `clone_from()` to reuse allocations
- - Reuse collections with `clear()` in loops
- - Avoid `format!()` when string literals work; use `write!()` instead
- - Use smallest integer type that fits
- - Assert hot type sizes to prevent regressions
+ See `rules/memory.md` for detailed rules with failure modes and examples.
## API Design
- - Use Builder pattern for complex construction; add `#[must_use]` to builder types
- - Use newtypes for type-safe distinctions (`UserId(u64)`, `Email`, `Url`)
- - Use typestate for compile-time state machines
- - Seal traits to prevent external implementations when appropriate
- - Use extension traits to add methods to foreign types
- - Parse into validated types at boundaries
- - Accept `impl Into<T>` for flexible string inputs, `impl AsRef<T>` for borrowed inputs
- - Add `#[must_use]` to `Result`-returning functions
- - Use `#[non_exhaustive]` for future-proof enums/structs
- - Implement `From`, not `Into` (auto-derived)
- - Implement `Default` for sensible defaults
- - Implement `Debug`, `Clone`, `PartialEq` eagerly
- - Gate `Serialize`/`Deserialize` behind feature flag
+ **Use Builder pattern for complex construction; add `#[must_use]` to builder types** because builders make optional parameters ergonomic, and `#[must_use]` prevents silently dropping configured builders.
+ **Use newtypes for type-safe distinctions (`UserId(u64)`, `Email`, `Url`)** because newtypes prevent mixing semantically different values that share the same underlying type.
+
+ **Use typestate for compile-time state machines** because typestate encodes valid state transitions in the type system, making illegal states unrepresentable.
+
+ **Seal traits to prevent external implementations when appropriate** because sealed traits let you add methods later without breaking downstream code.
+
+ **Use extension traits to add methods to foreign types** because extension traits add functionality without modifying the original type or requiring ownership.
+
+ **Parse into validated types at boundaries** because parsing once at the boundary and using validated types internally prevents invalid data from propagating.
+
+ **Accept `impl Into<T>` for flexible string inputs, `impl AsRef<T>` for borrowed inputs** because these patterns accept multiple input types without overloading.
+
+ **Add `#[must_use]` to `Result`-returning functions** because `#[must_use]` forces callers to handle errors instead of silently ignoring them.
+
+ **Use `#[non_exhaustive]` for future-proof enums/structs** because `#[non_exhaustive]` lets you add fields/variants later without breaking semver.
+
+ **Implement `From`, not `Into` (auto-derived)** because `From` is the source; `Into` is automatically implemented for free.
+
+ **Implement `Default` for sensible defaults** because `Default` enables `..Default::default()` struct update syntax and builder patterns.
+
+ **Implement `Debug`, `Clone`, `PartialEq` eagerly** because these traits are so commonly needed that deriving them early saves friction.
+
+ **Gate `Serialize`/`Deserialize` behind feature flag** because serialization dependencies (serde) are heavy; let users opt in only when needed.
+
+ See `rules/api-design.md` for detailed rules with failure modes and examples.
+
## Async/Await
- - Use Tokio for production async runtime
- - Never hold `Mutex`/`RwLock` across `.await`
- - Use `spawn_blocking` for CPU-intensive work
- - Use `tokio::fs` not `std::fs` in async code
- - Use `CancellationToken` for graceful shutdown
- - Use `tokio::join!` for parallel operations, `tokio::try_join!` for fallible
- - Use `tokio::select!` for racing/timeouts
- - Use bounded channels for backpressure
- - Use `JoinSet` for dynamic task groups
- - Clone data before await, release locks
+ **Use Tokio for production async runtime** because Tokio is the most mature, well-tested async runtime with comprehensive ecosystem support.
+ **Never hold `Mutex`/`RwLock` across `.await`** because holding a lock across an await point can cause deadlocks if the task yields while another task waits for the lock.
+
+ **Use `spawn_blocking` for CPU-intensive work** because `spawn_blocking` runs CPU-bound tasks on a separate thread pool, preventing blocking the async runtime.
+
+ **Use `tokio::fs` not `std::fs` in async code** because `tokio::fs` offloads blocking I/O to the thread pool, keeping the async runtime responsive.
+
+ **Use `CancellationToken` for graceful shutdown** because cancellation tokens propagate shutdown signals cleanly through async task hierarchies.
+
+ **Use `tokio::join!` for parallel operations, `tokio::try_join!` for fallible** because `join!` runs tasks concurrently and `try_join!` short-circuits on first error.
+
+ **Use `tokio::select!` for racing/timeouts** because `select!` waits on multiple futures and cancels losers, enabling timeouts and race patterns.
+
+ **Use bounded channels for backpressure** because bounded channels prevent producers from overwhelming consumers with unbounded buffering.
+
+ **Use `JoinSet` for dynamic task groups** because `JoinSet` manages a collection of tasks with uniform lifecycle management.
+
+ **Clone data before await, release locks** because holding data across await points while keeping locks causes deadlocks; clone to break the dependency.
+
+ See `rules/async.md` for detailed rules with failure modes and examples.
+
## Testing
- - Use `#[cfg(test)] mod tests { }` with `use super::*;`
- - Put integration tests in `tests/` directory
- - Structure tests as arrange/act/assert
- - Use `#[tokio::test]` for async tests
- - Use `proptest` for property-based testing
- - Use `mockall` for trait mocking; design dependencies as traits
- - Use RAII pattern (Drop) for test cleanup
- - Use `criterion` for benchmarking
- - Keep doc examples as executable tests
- - Use `#[should_panic]` for panic tests
- - Aim for 80%+ coverage; test edge cases and error paths
+ **Use `#[cfg(test)] mod tests { }` with `use super::*;`** because test modules compile only with `cargo test`, keeping test code out of production builds.
- ## Anti-Patterns
+ **Put integration tests in `tests/` directory** because integration tests exercise the public API from outside the crate, catching breaking changes.
- The 15 `anti-*` rules are indexed in the Rule Index above with full rationale and ❌/✅ examples in [`rules/anti-patterns.md`](rules/anti-patterns.md).
+ **Structure tests as arrange/act/assert** because this pattern makes test intent clear: setup, exercise, verify.
- ## Essential Commands
+ **Use `#[tokio::test]` for async tests** because `#[tokio::test]` provides the async runtime needed to execute async test code.
- ```bash
- cargo build --release # Build with optimizations
- cargo test # Run all tests
- cargo test -- --nocapture # Run tests with output
- cargo clippy -- -D warnings # Lint with warnings as errors
- cargo fmt --check # Check formatting
- cargo doc --no-deps # Generate documentation
- cargo bench # Run benchmarks
- cargo fix --edition # Auto-fix edition migration
- ```
+ **Use `proptest` for property-based testing** because property tests generate many inputs automatically, finding edge cases manual tests miss.
- ## Cargo.toml Release Profile
+ **Use `mockall` for trait mocking; design dependencies as traits** because traits enable dependency injection, making code testable with mocks.
- ```toml
- [profile.release]
- opt-level = 3
- lto = "fat"
- codegen-units = 1
- panic = "abort"
- strip = true
+ **Use RAII pattern (Drop) for test cleanup** because `Drop` ensures cleanup runs even if tests panic.
- [profile.dev.package."*"]
- opt-level = 3
- ```
+ **Use `criterion` for benchmarking** because Criterion provides statistically sound benchmarks with regression detection.
- ## References
+ **Keep doc examples as executable tests** because `cargo test --doc` runs doc examples, ensuring they stay synchronized with implementation.
- - `rules/` — 14 category files (179 rules total), one file per prefix group, read on demand
- - `references/compiler-errors.md` — Compiler error quick reference (E0382, E0502, E0716, etc.)
+ **Use `#[should_panic]` for panic tests** because `#[should_panic]` verifies that expected panics actually occur.
+
+ **Aim for 80%+ coverage; test edge cases and error paths** because coverage metrics reveal untested code, and error paths are where bugs hide.
+
+ See `rules/testing.md` for detailed rules with failure modes and examples.
+
+ ## Anti-Patterns
+
+ The 15 `anti-*` rules are indexed in `references/rule-index.md` with full rationale and ❌/✅ examples in [`rules/anti-patterns.md`](rules/anti-patterns.md).
+
+ Common anti-patterns to avoid:
+
+ - **Don't use `.unwrap()` in production code** because panics crash instead of recovering
+ - **Don't clone when borrowing works** because cloning wastes memory and CPU
+ - **Don't hold locks across `.await`** because this causes deadlocks
+ - **Don't accept `&String` when `&str` works** because this restricts caller flexibility
+ - **Don't use `format!()` in hot paths** because allocation in hot paths degrades performance
+
+ ## Deep Dives
+
+ See these reference files for detailed catalogs:
+
+ - `references/rule-index.md` — Full 179-rule catalog across all categories
+ - `references/compiler-errors.md` — Common compiler errors (E0382, E0502, E0716) and quick fixes
+ - `references/commands.md` — Essential Cargo commands for build/test/lint
+ - `references/release-profile.md` — Optimized release profile configuration