templates/skills/effective-rust/SKILL.md
Expert Rust developer specializing in ownership semantics, zero-cost abstractions, and idiomatic patterns. This skill should be used PROACTIVELY when working on any Rust code - implementing features, debugging borrow checker issues, optimizing performance, or reviewing code quality. Use unless a more specific subagent role applies.
npx skillsauth add samwang0723/claudecode-setup effective-rustInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Senior-level Rust expertise following "Boring Rust" principles. Correctness over cleverness. One way to do things. Local reasoning.
Cargo.toml, clippy.toml, and rustfmt.toml for project conventionsNon-Negotiable:
unwrap() or expect() in production code — use .context("...")?unsafe without #[human_authored] designation_ on enums you controlFoundational Principles:
All agent-generated code. Maximum guardrails.
// Complexity limits enforced:
// - Cognitive complexity: 15 max
// - Function lines: 50 max
// - Arguments: 5 max
// Error handling: Always with context
let config = load_config(path)
.context("failed to load configuration")?;
// Iteration: for loops by default (not iterator chains)
for item in collection {
process(item)?;
}
// Matching: Exhaustive, no wildcards
match state {
State::Active => handle_active()?,
State::Pending => handle_pending()?,
State::Done => handle_done()?,
// NO: _ => unreachable!()
}
#[hot_path] (Relaxed)Performance-critical code. Flagged for human review.
#[hot_path]
pub fn process_batch(records: &[Record]) -> Result<Summary, Error> {
// Allowed: iterators, borrowing, fewer clones
records.iter()
.filter(|r| r.is_valid())
.try_fold(Summary::default(), |mut acc, r| {
acc.add(r)?;
Ok(acc)
})
}
Relaxations: Cognitive complexity 20, function lines 75, iterator chains allowed.
#[human_authored] (Unrestricted)Agent cannot modify, only call. For unsafe, SIMD, complex generics.
#[human_authored]
pub fn simd_normalize(vectors: &mut [f32x8]) {
// Agent treats as black box
}
mise manages language runtimes per-project. For Rust, this complements rustup by pinning the exact toolchain version.
# Install mise (once)
curl https://mise.run | sh
# In project root
mise use [email protected]
# Creates .mise.toml — commit it
# Team members just run: mise install
Alternatively, use rust-toolchain.toml (rustup-native) if you prefer not to add mise as a dependency.
# Initialize
cargo new project-name && cd project-name
# Copy configs from this skill's references/ directory:
# references/gitignore → .gitignore
# references/clippy.toml → clippy.toml
# references/cargo_lints.toml → merge into Cargo.toml [lints] section
# references/rustfmt.toml → rustfmt.toml
# For build system, invoke just-pro skill
# Verify
just check # Or: cargo clippy && cargo test
git clone <repo> && cd <repo>
just setup # Runs mise trust/install + cargo build
just check # Verify everything works
Or manually:
mise trust && mise install # Get pinned Rust toolchain
cargo build # Get dependencies
Why Boring Rust? Agent-generated code that compiles is usually correct. Complex patterns cause agents to produce incorrect or unmaintainable code.
Invoke the just-pro skill for build system setup. It covers:
Why just? Consistent toolchain frontend between agents and humans.
Auto-Fix First:
just fix # Or: cargo clippy --fix && cargo fmt
Verification:
just check # Or: cargo clippy --all-targets -- -D warnings && cargo test
Use --all-targets to lint tests, examples, and benches too.
// Libraries: thiserror for typed errors
#[derive(Debug, thiserror::Error)]
pub enum ConfigError {
#[error("missing field: {field}")]
MissingField { field: &'static str },
#[error("failed to read file")]
Io(#[from] std::io::Error),
}
// Applications: anyhow with context
pub fn load_config(path: &Path) -> anyhow::Result<Config> {
let content = fs::read_to_string(path)
.context("failed to read config file")?;
toml::from_str(&content)
.context("failed to parse config")
}
// Option handling: explicit, never silent
let user = users.get(&id)
.ok_or_else(|| Error::NotFound { id: id.clone() })?;
pub enum ConnectionState {
Disconnected,
Connecting { attempt: u32, started: Instant },
Connected { session: Session },
}
impl ConnectionState {
pub fn connect(&mut self) -> Result<(), Error> {
match self {
Self::Disconnected => {
*self = Self::Connecting {
attempt: 1,
started: Instant::now(),
};
Ok(())
}
Self::Connecting { .. } => Err(Error::AlreadyConnecting),
Self::Connected { .. } => Err(Error::AlreadyConnected),
}
}
}
use bon::Builder;
#[derive(Debug, Builder)]
pub struct ServerConfig {
#[builder(default = 8080)]
port: u16,
host: String, // Required
#[builder(default)]
timeout: Option<Duration>,
}
let config = ServerConfig::builder()
.host("localhost".to_string())
.build();
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct UserId(String);
impl UserId {
pub fn new(raw: impl Into<String>) -> Result<Self, ValidationError> {
let s = raw.into();
if s.is_empty() {
return Err(ValidationError::Empty("user_id"));
}
Ok(Self(s))
}
pub fn as_str(&self) -> &str { &self.0 }
}
// GOOD: Owned data in, owned data out
pub async fn fetch_user(client: &Client, id: UserId) -> Result<User, Error> {
let response = client
.get(format!("/users/{}", id.as_str()))
.send()
.await
.context("request failed")?;
response.json::<User>().await
.context("failed to parse response")
}
// GOOD: Structured concurrency
pub async fn fetch_all(client: &Client, ids: Vec<UserId>) -> Result<Vec<User>, Error> {
futures::future::try_join_all(
ids.into_iter().map(|id| fetch_user(client, id))
).await
}
// BANNED: Complex lifetime bounds in async
async fn bad<'a>(data: &'a [u8]) -> &'a str { ... }
// BANNED: select!, manual Poll
// src/parser.rs - production code only, keeps file small
#[cfg(test)]
#[path = "parser_tests.rs"]
mod tests;
// src/parser_tests.rs - can have test relaxations
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[test]
fn test_parser() {
let result = parse("input").unwrap();
assert_eq!(result, expected);
}
project/
├── src/
│ ├── lib.rs # Crate root
│ ├── error.rs # Error types
│ ├── config.rs # Production code
│ ├── config_tests.rs # Tests (if config.rs > 200 lines)
│ └── external/ # Wrappers around external crates
├── Cargo.toml
├── clippy.toml
├── rustfmt.toml
└── justfile
File size targets: Production < 300 LOC, Tests < 500 LOC.
| Banned | Why | Alternative |
|--------|-----|-------------|
| .unwrap() | Panics | .context("...")? |
| .expect("msg") | Panics | .context("msg")? |
| array[i] | Panics | .get(i).ok_or(Error::Index)? |
| unsafe { } | Correctness | #[human_authored] module |
| impl Trait in params | Hides types | <T: Trait> explicit |
| macro_rules! | Complexity | Functions or generics |
| RefCell<T> | Runtime borrow | Restructure with &mut |
| Complex lifetimes | Agent confusion | Clone or restructure |
| select! | Cancellation bugs | Structured concurrency |
| Wildcard _ match | Silent failures | Explicit variants |
| Iterator chains (Tier 1) | Harder to debug | for loops |
clone() to silence borrow checker without understanding why#[allow(...)] without // JUSTIFICATION: commentRefCell, Cell) in agent code| Category | Crate | Notes |
|----------|-------|-------|
| Errors (lib) | thiserror | Derive-based |
| Errors (app) | anyhow | With .context() |
| Builder | bon | Derive-based |
| Serialization | serde | Standard |
| Async runtime | tokio | Blessed subset only |
| HTTP framework | axum | Tower-based, extractors |
| HTTP client | reqwest | High-level |
| Database | sqlx | Compile-time checked queries |
| Logging | tracing | Structured spans |
| HTTP tracing | tower-http | TraceLayer, TimeoutLayer |
| Validation | validator | Derive-based |
| CLI | clap | Derive mode |
| Testing | tokio::test | Async test runtime |
Before writing code:
Cargo.toml for dependencies and lint configurationclippy.toml for complexity thresholdsWhen writing code:
.context("what you were doing")?for loops, not iterator chains (unless #[hot_path])_ on your own enumsBefore committing:
just check (standard for projects using just)cargo clippy -- -D warnings && cargo test#[allow] without justification commentClippy and rustfmt walk up directory trees looking for config files. A rogue config in a parent directory (like /tmp) can break your project.
Symptoms:
unknown field errors from clippyFix: Create project-local configs to prevent inheritance:
# clippy.toml - prevents inheriting parent configs
# (empty file is valid)
# rustfmt.toml - minimal stable config
edition = "2024"
cargo init now defaults to edition 2024. If referencing older templates, update them.
references/services.md — Axum service patterns (handlers, AppError, sqlx repos, auth middleware, config, testing)references/patterns.md — Ownership, state machines, traits, concurrency, testingreferences/clippy.toml — Boring Rust clippy configurationreferences/cargo_lints.toml — Cargo.toml [lints] sectionreferences/rustfmt.toml — Formatting rulesreferences/bevy.md — Bevy ECS patterns (game development)development
Generate a technical specification document using the DDD template in template.md. Use when the user says 'write tech spec', 'create tech spec', 'technical specification', or needs a structured design document for a new feature or major change covering architecture, domain models, APIs, data design, security, and operations.
testing
Write a Product Requirements Document (PRD) using the standard TMAB template with stakeholders, user stories (Given-When-Then), success metrics, and A/B testing plans. Use when the user asks to write a PRD, create product requirements, document a feature spec, or plan a new product feature.
data-ai
Clean up an agent team. Removes team resources, optionally cleans worktrees and branches. Use after team work is complete and merged. Use when told to "stop team", "cleanup team", "disband team".
testing
Check progress of an active agent team. Shows member status, completed tasks, pending work, and any messages. Use when asked "team status", "how's the team", "check team progress", "team update".