SKILL.md


name: writing-rust description: Writes idiomatic, well-structured Rust code following community conventions and best practices. Use when working on Rust projects, writing Rust code, debugging Rust errors, or when the user mentions Rust, Cargo, or crates. license: GPL-3.0-or-later metadata: author: Amolith amolith@secluded.site

Write idiomatic Rust. All code must compile, pass clippy, and have tests.

Code quality checks

Run these in order before considering any code complete. Never submit code that fails any check.

cargo fmt
cargo check
cargo clippy -- -D warnings
cargo test

Project structure

project-root/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs              # Binary entry point (if applicable)
│   ├── lib.rs               # Library root (if applicable)
│   └── module_name/
│       ├── mod.rs
│       └── submodule.rs
├── tests/                   # Integration tests
├── benches/                 # Benchmarks (if applicable)
└── examples/                # Example usage (if applicable)

Naming conventions

Item Convention Example
Crates snake_case my_crate
Modules snake_case my_module
Types / Traits PascalCase MyStruct, MyTrait
Functions snake_case do_something()
Constants SCREAMING_SNAKE MAX_RETRIES
Local variables snake_case item_count
Type parameters Single uppercase T, E, K, V
Lifetimes Short lowercase 'a, 'de

Error handling

  • Prefer Result<T, E> over panic!() for recoverable errors.
  • Use thiserror for library error types, anyhow for application-level errors.
  • Avoid .unwrap() and .expect() in production code. Propagate with ?.
  • Define custom error types when a module has more than one failure mode.
  • When a fallible function consumes an argument, return it inside the error type so callers can recover it.

Ownership and borrowing

  • Prefer borrowing (&T, &mut T) over transferring ownership when the caller doesn't need to give up the value.
  • Use Clone sparingly — never just to satisfy the borrow checker.
  • Prefer &str over String, &[T] over &Vec<T>, and &T over &Box<T> in function parameters.
  • Use Cow<'_, str> when a function may or may not need to allocate.
  • Use mem::take or mem::replace to move values out of mutable references without cloning.

Structs and enums

  • Derive common traits where appropriate: Debug, Clone, PartialEq, Eq, Hash, Default.
  • Use the builder pattern for structs with many optional fields.
  • Prefer enums over boolean flags for state representation.
  • Use #[non_exhaustive] on public structs and enums that may grow over time.
  • Use the newtype pattern for type safety wrappers around primitives (zero-cost abstraction).
  • Decompose large structs into smaller ones when the borrow checker prevents independent field access.

Testing

  • All implemented functionality must have corresponding tests.
  • Tests must validate behavior, not just assert true.
  • Use descriptive test names that explain what is being tested.
  • Test both the happy path and error/edge cases.
cargo test                  # Run all tests
cargo test test_name        # Run a specific test
cargo test -- --nocapture   # Run tests with output

Design principles

  • KISS: Simplicity should be a key goal in design.
  • YAGNI: Don't add functionality until it is necessary.
  • DRY: Every piece of knowledge should have a single, authoritative representation.
  • SOLID: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion.
  • Composition over inheritance: Favour traits and composition over emulating inheritance.
  • Command-Query Separation: Functions should either return data or produce side effects, not both.
  • Principle of Least Astonishment: Components should behave the way most users expect.

Performance

  • Avoid unnecessary heap allocations. Prefer stack allocation and slices.
  • Use &[T] instead of &Vec<T> and &str instead of &String in function signatures.
  • Profile before optimizing. Use cargo bench and tools like criterion.
  • Stay lazy with iterators. Avoid .collect()-ing unnecessarily.

Dependencies

  • Prefer well-maintained, widely-used crates. Pin versions in Cargo.toml.
  • Minimize the dependency tree. Avoid adding crates for trivial functionality.
  • Use cargo audit to check for known vulnerabilities.

Documentation

  • All public items (pub) must have doc comments.
  • Include examples in doc comments for public functions.
  • Use module-level documentation at the top of files.

Anti-patterns

Common but counterproductive solutions. Avoid them.

Clone to satisfy the borrow checker

Do not resolve borrow checker errors by cloning without understanding the consequences. Cloning creates independent copies — changes to one are not reflected in the other.

If the borrow checker complains, first understand the ownership issue. Use mem::take, restructure borrows, or redesign the data flow.

Exception: Rc and Arc are designed for shared ownership via clone. Cloning them is cheap and correct. Deliberate cloning is also fine when ownership semantics require it, or for prototypes and non-performance-critical code.

#![deny(warnings)]

Do not use #![deny(warnings)] in crate roots. New compiler versions may introduce new warnings, breaking builds unexpectedly.

Instead, deny specific named lints explicitly, or use RUSTFLAGS="-D warnings" in CI. This preserves Rust's stability guarantees while still enforcing lint discipline.

Deref polymorphism

Do not misuse the Deref trait to emulate struct inheritance. Deref is designed for smart pointers (pointer-to-TT), not for converting between arbitrary types.

It does not introduce subtyping. Traits on the inner type are not automatically available on the outer type. It interacts badly with generics and bounds checking.

Instead, use composition with explicit delegation methods, or use traits for shared behaviour.

Further reference

Before implementing any non-trivial pattern — builders, visitors, newtype wrappers, RAII guards, command objects, struct decomposition for borrow splitting, etc. — read idioms.md. It covers the community-agreed idioms and design patterns for Rust, and getting these right the first time avoids painful refactors later.