mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
* feat(rules): add Rust coding style, hooks, and patterns rules Add language-specific rules for Rust extending the common rule set: - coding-style.md: rustfmt, clippy, ownership idioms, error handling, iterator patterns, module organization, visibility - hooks.md: PostToolUse hooks for rustfmt, clippy, cargo check - patterns.md: trait-based repository, newtype, enum state machines, builder, sealed traits, API response envelope Rules reference existing rust-patterns skill for deep content. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> * feat(rules): add Rust testing and security rules Add remaining Rust language-specific rules: - testing.md: cargo test, rstest parameterized tests, mockall mocking with mock! macro, tokio async tests, cargo-llvm-cov coverage - security.md: secrets via env vars, parameterized SQL with sqlx, parse-don't-validate input validation, unsafe code audit requirements, cargo-audit dependency scanning, proper HTTP error status codes Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> * fix(rules): address review feedback on Rust rules Fixes from Copilot, Greptile, Cubic, and CodeRabbit reviews: - Add missing imports: use std::borrow::Cow, use anyhow::Context - Use anyhow::Result<T> consistently (patterns.md, security.md) - Change sqlx placeholder from ? to $1 (Postgres is most common) - Remove Cargo.lock from hooks.md paths (auto-generated file) - Fix tokio::test to show attribute form #[tokio::test] - Fix mockall mock! name collision, wrap in #[cfg(test)] mod tests - Fix --test target to match file layout (api_test, not integration) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> * fix: update catalog counts in README.md and AGENTS.md Update documented counts to match actual repository state after rebase: - Skills: 109 → 113 (new skills merged to main) - Commands: 57 → 58 (new command merged to main) Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> --------- Co-authored-by: Chris Yau <chris@diveanddev.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Happy <yesreply@happy.engineering>
152 lines
4.1 KiB
Markdown
152 lines
4.1 KiB
Markdown
---
|
|
paths:
|
|
- "**/*.rs"
|
|
---
|
|
# Rust Coding Style
|
|
|
|
> This file extends [common/coding-style.md](../common/coding-style.md) with Rust-specific content.
|
|
|
|
## Formatting
|
|
|
|
- **rustfmt** for enforcement — always run `cargo fmt` before committing
|
|
- **clippy** for lints — `cargo clippy -- -D warnings` (treat warnings as errors)
|
|
- 4-space indent (rustfmt default)
|
|
- Max line width: 100 characters (rustfmt default)
|
|
|
|
## Immutability
|
|
|
|
Rust variables are immutable by default — embrace this:
|
|
|
|
- Use `let` by default; only use `let mut` when mutation is required
|
|
- Prefer returning new values over mutating in place
|
|
- Use `Cow<'_, T>` when a function may or may not need to allocate
|
|
|
|
```rust
|
|
use std::borrow::Cow;
|
|
|
|
// GOOD — immutable by default, new value returned
|
|
fn normalize(input: &str) -> Cow<'_, str> {
|
|
if input.contains(' ') {
|
|
Cow::Owned(input.replace(' ', "_"))
|
|
} else {
|
|
Cow::Borrowed(input)
|
|
}
|
|
}
|
|
|
|
// BAD — unnecessary mutation
|
|
fn normalize_bad(input: &mut String) {
|
|
*input = input.replace(' ', "_");
|
|
}
|
|
```
|
|
|
|
## Naming
|
|
|
|
Follow standard Rust conventions:
|
|
- `snake_case` for functions, methods, variables, modules, crates
|
|
- `PascalCase` (UpperCamelCase) for types, traits, enums, type parameters
|
|
- `SCREAMING_SNAKE_CASE` for constants and statics
|
|
- Lifetimes: short lowercase (`'a`, `'de`) — descriptive names for complex cases (`'input`)
|
|
|
|
## Ownership and Borrowing
|
|
|
|
- Borrow (`&T`) by default; take ownership only when you need to store or consume
|
|
- Never clone to satisfy the borrow checker without understanding the root cause
|
|
- Accept `&str` over `String`, `&[T]` over `Vec<T>` in function parameters
|
|
- Use `impl Into<String>` for constructors that need to own a `String`
|
|
|
|
```rust
|
|
// GOOD — borrows when ownership isn't needed
|
|
fn word_count(text: &str) -> usize {
|
|
text.split_whitespace().count()
|
|
}
|
|
|
|
// GOOD — takes ownership in constructor via Into
|
|
fn new(name: impl Into<String>) -> Self {
|
|
Self { name: name.into() }
|
|
}
|
|
|
|
// BAD — takes String when &str suffices
|
|
fn word_count_bad(text: String) -> usize {
|
|
text.split_whitespace().count()
|
|
}
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
- Use `Result<T, E>` and `?` for propagation — never `unwrap()` in production code
|
|
- **Libraries**: define typed errors with `thiserror`
|
|
- **Applications**: use `anyhow` for flexible error context
|
|
- Add context with `.with_context(|| format!("failed to ..."))?`
|
|
- Reserve `unwrap()` / `expect()` for tests and truly unreachable states
|
|
|
|
```rust
|
|
// GOOD — library error with thiserror
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum ConfigError {
|
|
#[error("failed to read config: {0}")]
|
|
Io(#[from] std::io::Error),
|
|
#[error("invalid config format: {0}")]
|
|
Parse(String),
|
|
}
|
|
|
|
// GOOD — application error with anyhow
|
|
use anyhow::Context;
|
|
|
|
fn load_config(path: &str) -> anyhow::Result<Config> {
|
|
let content = std::fs::read_to_string(path)
|
|
.with_context(|| format!("failed to read {path}"))?;
|
|
toml::from_str(&content)
|
|
.with_context(|| format!("failed to parse {path}"))
|
|
}
|
|
```
|
|
|
|
## Iterators Over Loops
|
|
|
|
Prefer iterator chains for transformations; use loops for complex control flow:
|
|
|
|
```rust
|
|
// GOOD — declarative and composable
|
|
let active_emails: Vec<&str> = users.iter()
|
|
.filter(|u| u.is_active)
|
|
.map(|u| u.email.as_str())
|
|
.collect();
|
|
|
|
// GOOD — loop for complex logic with early returns
|
|
for user in &users {
|
|
if let Some(verified) = verify_email(&user.email)? {
|
|
send_welcome(&verified)?;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Module Organization
|
|
|
|
Organize by domain, not by type:
|
|
|
|
```text
|
|
src/
|
|
├── main.rs
|
|
├── lib.rs
|
|
├── auth/ # Domain module
|
|
│ ├── mod.rs
|
|
│ ├── token.rs
|
|
│ └── middleware.rs
|
|
├── orders/ # Domain module
|
|
│ ├── mod.rs
|
|
│ ├── model.rs
|
|
│ └── service.rs
|
|
└── db/ # Infrastructure
|
|
├── mod.rs
|
|
└── pool.rs
|
|
```
|
|
|
|
## Visibility
|
|
|
|
- Default to private; use `pub(crate)` for internal sharing
|
|
- Only mark `pub` what is part of the crate's public API
|
|
- Re-export public API from `lib.rs`
|
|
|
|
## References
|
|
|
|
See skill: `rust-patterns` for comprehensive Rust idioms and patterns.
|