Transform hub-guide from a single-skill Hub monorepo guide into a comprehensive programming best-practice plugin covering all situations. Skills (26): - Core: engineering-principles, clean-code, clean-architecture, design-patterns, testing, error-handling, security, api-design, git-workflow, documentation, logging-observability, performance - Languages: typescript, python, rust, go - Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs - Infrastructure: docker, ci-cd, monitoring - Monorepo: monorepo, hub-guide (existing) Hooks: - SessionStart: auto-detect project type and activate relevant skills - PreToolUse (Write|Edit): inject language-specific rules per file type Reference files for deep dives: - clean-architecture/references/solid.md (SOLID + component principles) - design-patterns/references/catalog.md (full GoF catalog with examples) - testing/references/mocks.md (test double taxonomy) Restructure plugin to modern skills/ directory format.
5.4 KiB
5.4 KiB
name, description
| name | description |
|---|---|
| rust | Rust best practices — ownership, error handling, async, project structure, clippy rules, testing, and idiomatic Rust. Use when writing Rust code, reviewing Rust projects, or whenever the user mentions "Rust," "cargo," "clippy," "ownership," "borrowing," "lifetimes," "Result," "Option," "async/await," "tokio," "Axum," "SeaORM," or "unsafe." |
Rust Best Practices
Project Structure (Clean Architecture)
src/
├── domain/ # Entities, value objects, domain events
├── application/ # Use cases, repository ports (traits)
├── infrastructure/ # Adapters (DB, HTTP client, cache)
├── api/ # Axum/Actix handlers, middleware
├── config/ # App configuration
└── main.rs # Entry point, composition root
Error Handling
// Use thiserror for application errors
use thiserror::Error;
#[derive(Error, Debug)]
pub enum OrderError {
#[error("order not found: {0}")]
NotFound(String),
#[error("validation error: {0}")]
Validation(String),
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
}
// Use Result in application layer
pub async fn create_order(input: CreateOrderInput) -> Result<Order, OrderError> {
let user = user_repo.find_by_id(&input.user_id)
.await?
.ok_or_else(|| OrderError::NotFound("user".into()))?;
// ...
}
Rules:
thiserrorfor library/app errors.anyhowfor binary/CLI errors.- Never
unwrap()orexpect()in production code. Use?or match. Resultfor recoverable errors.panic!only for unrecoverable states.
Ownership & Borrowing
// Prefer borrowing over taking ownership
fn process(items: &[Item]) -> usize {
items.iter().filter(|i| i.active).count()
}
// Clone when ownership is truly needed (and it actually needs to be owned)
fn save(items: Vec<Item>) { ... }
- One mutable reference (
&mut) XOR many immutable refs (&). - Use
Cowfor "borrow if possible, own if modified." - Prefer
&strover&Stringfor function params. - Rc/Arc — only when shared ownership is needed (graphs, caches).
Async (Tokio)
#[tokio::main]
async fn main() -> Result<()> {
// ...
Ok(())
}
// Use tokio::spawn for concurrent tasks
let handle = tokio::spawn(async move {
process_batch(items).await
});
let result = handle.await??;
Rules:
tokiofor runtime. Axum for HTTP.tokio::select!for timeouts, race conditions.- No
block_onin async code. No sync mutex in async — usetokio::sync::Mutex. - Use
tokio::sync::Semaphorefor rate limiting concurrent tasks.
Traits (Interfaces)
// Port — defined in domain/application layer
#[async_trait]
pub trait UserRepository: Send + Sync {
async fn find_by_id(&self, id: &str) -> Result<Option<User>, DbError>;
async fn save(&self, user: &User) -> Result<(), DbError>;
}
// Adapter — implemented in infrastructure layer
pub struct PostgresUserRepository {
pool: sqlx::PgPool,
}
#[async_trait]
impl UserRepository for PostgresUserRepository {
async fn find_by_id(&self, id: &str) -> Result<Option<User>, DbError> {
sqlx::query_as("SELECT * FROM users WHERE id = $1")
.bind(id)
.fetch_optional(&self.pool)
.await
.map_err(Into::into)
}
}
Database (sqlx / SeaORM)
// sqlx — prefer raw SQL with compile-time checking
let user = sqlx::query_as::<_, User>(
"SELECT id, email, name FROM users WHERE id = $1"
)
.bind(user_id)
.fetch_optional(&pool)
.await?;
- sqlx for type-safe raw SQL. SeaORM when you need a full ORM.
- Use migrations (
sqlx migrateorsea-orm-cli). - Connection pooling via
sqlx::PgPoolordeadpool.
Testing
// Unit tests inline
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn order_total_calculates_correctly() {
let order = Order::new(vec![
OrderItem::new(10.0, 2),
OrderItem::new(5.0, 1),
]);
assert_eq!(order.total(), 25.0);
}
#[tokio::test]
async fn create_order_requires_user() {
let repo = InMemoryUserRepo::new();
let result = create_order(CreateOrderInput { user_id: "nonexistent".into() }, &repo).await;
assert!(result.is_err());
}
}
// Integration tests in tests/
- Unit tests inline in module. Integration in
tests/directory. - Fakes (in-memory repos) over mocking libraries.
- Property-based testing with
proptestfor complex logic.
Clippy Rules
# .clippy.toml
# Enable in Cargo.toml or rustfmt.toml:
# [lints.clippy]
# pedantic = "warn"
# nursery = "warn"
Default: #![warn(clippy::pedantic, clippy::nursery)]
Serialization (serde)
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct UserResponse {
pub id: String,
pub email: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
Anti-patterns
- ❌
unwrap()/expect()in production — crash on any error - ❌ Large
main.rs— everything in one file - ❌
unsafewithout documented safety invariants - ❌
Rc<RefCell<...>>in async contexts — useArc<Mutex<...>> - ❌
Box<dyn Trait>where generics work —impl Traitor generic param - ❌ Ignoring clippy warnings — run
clippybefore every commit - ❌
#[tokio::main]on library code — only in binary entry points