1---2name: rust-53description: Rust coding conventions. Apply when writing, reviewing, or modifying Rust code. Covers error handling, types, ownership, testing, and anti-patterns.4---5
6# Rust Coding Conventions
7
8## Error Handling
9
10- Return `Result<T, E>` for fallible operations
11- Use `thiserror` with `#[derive(Error)]` for library error enums. Structure: variant name describes the failure, `#[error("...")]` format string includes diagnostic context (sizes, IDs), use `#[from]` for transparent wrapping of upstream errors
12- Propagate with `?` operator — avoid match chains for error forwarding
13- Never use `.unwrap()` or `.expect()` outside of tests
14- `anyhow` for binary/CLI code where specific error types don't matter; never in library crates
15
16## Borrowing & Ownership
17
18- Prefer `&T` over `.clone()` unless ownership transfer is required
19- Use `&str` over `String`, `&[T]` over `Vec<T>` in function parameters
20- Small `Copy` types (<=24 bytes) can be passed by value
21- Use `Cow<'_, T>` when ownership is ambiguous at compile time
22- If you're adding `.clone()` to satisfy the borrow checker, step back and reconsider the data flow
23
24## Type Design
25
26- Use newtypes for semantic distinction: `pub struct UserId(pub Uuid)`, `pub struct Port(pub u16)`
27- Derive ordering: `Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize` — include only what's semantically correct
28- `Copy` for small value types (IDs, enums). `Clone` but not `Copy` for larger types
29- Implement `Display` manually when the type appears in logs or user-facing output
30
31## Documentation Style
32
33- `///` doc comments on all public types, functions, and enum variants — explain **what** and **how**
34- `//` inline comments only to explain **why** (safety invariants, workarounds, design rationale)
35- Function docs should include format specs when relevant (e.g., wire format, encoding details)
36- Group related constants with `// --- Section Name ---` separators
37- Every `TODO` needs a linked issue: `// TODO(#42): description`
38- No module-level `//!` doc comments unless the module is a public API entry point
39
40## Testing Conventions
41
42- Tests live inline: `#[cfg(test)] mod tests { }` at the bottom of each module
43- Test naming: `snake_case_description` — be descriptive (e.g., `buffer_duplicate_insert_ignored`)
44- For serialization testing, use round-trip helpers that encode then decode and assert equality
45- `#[tokio::test]` for async tests — always with timeouts
46- One assertion per test when possible; multiple related assertions are fine if testing one logical behavior
47- Test error conditions explicitly — assert the specific error variant, not just "is error"
48- Use polling with backoff for async assertions, never fixed `sleep`
49
50## Iterators & Collections
51
52- Prefer iterators (`.iter()`, `.map()`, `.filter()`) over index-based loops
53- Avoid intermediate `.collect()` — chain iterators directly
54- Use `.iter()` for `Copy` types, `.into_iter()` when consuming ownership
55
56## Clippy & Linting
57
58- Run `cargo clippy` after every change
59- Use `#[expect(clippy::lint_name)]` over `#[allow(...)]` — `expect` warns if the lint no longer triggers
60- Key lints to watch: `redundant_clone`, `large_enum_variant` (consider `Box`), `needless_collect`
61
62## Anti-Patterns
63
64| Anti-Pattern | Why Bad | Better |
65|---|---|---|
66| `.clone()` everywhere | Hides ownership issues | Proper references or restructure data flow |
67| `.unwrap()` in library code | Runtime panics | `?`, or handle the error |
68| `String` in function params | Unnecessary allocation | `&str`, `Cow<str>` |
69| Index-based loops | Error-prone, unidiomatic | Iterators |
70| `Rc`/`Arc` when single owner | Unnecessary overhead | Simple ownership |
71| Giant match arms | Unmaintainable | Extract to methods |
72| Ignoring `#[must_use]` | Silently dropped errors | Handle or `let _ =` |
73| `unsafe` without SAFETY comment | UB risk, no audit trail | Document invariants or find safe pattern |