rust-core · v2.0.0 · 2026-10-28 · sha256 db755bf113008b3c

rust-core v2.0.0A

Immutable. This exact content never changes and is served at /api/v1/blob/db755bf113008b3c.

---
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
---

> **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.

# Rust Core

Guide for writing high-quality, idiomatic, and optimized Rust code. Targets Rust 1.80+ and Edition 2024.

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.

See `references/rule-index.md` for the full 179-rule catalog.

## Ownership & Borrowing

**Prefer `&T` borrowing over `.clone()`** because cloning allocates and copies memory unnecessarily when a reference suffices, wasting CPU and memory in hot paths.

**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.

**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.

**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).

**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.

**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).

**Move large data instead of cloning** because moving transfers ownership with zero cost, while cloning always allocates and copies.

**Rely on lifetime elision when possible** because explicit lifetimes add verbosity; the compiler infers common patterns correctly in most cases.

See `rules/ownership.md` for detailed rules with failure modes and examples.

## Error Handling

**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).

**Return `Result`, don't panic on expected errors** because panics crash the entire program while `Result` lets callers recover or propagate gracefully.

**Add context with `.context()` or `.with_context()`** because error chains show what operation failed and why, making debugging production incidents feasible.

**Never use `.unwrap()` in production code** because `.unwrap()` panics when the value is `None` or `Err`, turning recoverable errors into crashes.

**Use `.expect()` only for programming errors** because `.expect("invariant violated")` documents invariants that should never fail if the code is correct.

**Use `?` operator for clean propagation** because `?` propagates errors without boilerplate, keeping error paths readable.

**Use `#[from]` for automatic error conversion** because `#[from]` generates `From` implementations, reducing glue code for common error types.

**Use `#[source]` to chain underlying errors** because `#[source]` preserves the error chain for debugging, enabling `error.source()` traversal.

**Error messages: lowercase, no trailing punctuation** because Rust convention uses lowercase for error messages (like `std`), and punctuation adds noise.

**Document errors with `# Errors` section** because users need to know what failures to expect and how to handle them.

**Create custom error types, not `Box<dyn Error>`** because custom types enable pattern matching, better diagnostics, and stable APIs.

See `rules/error-handling.md` for detailed rules with failure modes and examples.

## Memory Optimization

**Use `with_capacity()` when size is known** because pre-allocating avoids repeated reallocations as the collection grows.

**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.

**Box large enum variants to reduce type size** because boxing the largest variant makes the enum size predictable and often smaller overall.

**Use `Box<[T]>` instead of `Vec<T>` when fixed size** because `Box<[T]>` cannot grow/shrink, communicating intent and avoiding capacity overhead.

**Use `clone_from()` to reuse allocations** because `clone_from()` can reuse existing buffers instead of allocating fresh memory.

**Reuse collections with `clear()` in loops** because `clear()` keeps capacity, avoiding repeated allocations in hot loops.

**Avoid `format!()` when string literals work; use `write!()` instead** because `format!()` always allocates, while `write!()` can write to existing buffers.

**Use smallest integer type that fits** because smaller types reduce memory footprint and can improve cache locality.

**Assert hot type sizes to prevent regressions** because `assert!(std::mem::size_of::<T>() <= N)` catches accidental bloat in performance-critical types.

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** 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** 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::*;`** because test modules compile only with `cargo test`, keeping test code out of production builds.

**Put integration tests in `tests/` directory** because integration tests exercise the public API from outside the crate, catching breaking changes.

**Structure tests as arrange/act/assert** because this pattern makes test intent clear: setup, exercise, verify.

**Use `#[tokio::test]` for async tests** because `#[tokio::test]` provides the async runtime needed to execute async test code.

**Use `proptest` for property-based testing** because property tests generate many inputs automatically, finding edge cases manual tests miss.

**Use `mockall` for trait mocking; design dependencies as traits** because traits enable dependency injection, making code testable with mocks.

**Use RAII pattern (Drop) for test cleanup** because `Drop` ensures cleanup runs even if tests panic.

**Use `criterion` for benchmarking** because Criterion provides statistically sound benchmarks with regression detection.

**Keep doc examples as executable tests** because `cargo test --doc` runs doc examples, ensuring they stay synchronized with implementation.

**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