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>overpanic!()for recoverable errors. - Use
thiserrorfor library error types,anyhowfor 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
Clonesparingly — never just to satisfy the borrow checker. - Prefer
&stroverString,&[T]over&Vec<T>, and&Tover&Box<T>in function parameters. - Use
Cow<'_, str>when a function may or may not need to allocate. - Use
mem::takeormem::replaceto 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&strinstead of&Stringin function signatures. - Profile before optimizing. Use
cargo benchand tools likecriterion. - 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 auditto 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-T → T), 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.