Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component...
npx skills add majiayu000/claude-arsenal --skill "rust-project"
Install specific skill from multi-skill repository
# Description
Modern Rust project architecture guide for 2025. Use when creating Rust projects (CLI, web services, libraries). Covers workspace structure, error handling, async patterns, and idiomatic Rust best practices.
# SKILL.md
name: rust-project
description: Modern Rust project architecture guide for 2025. Use when creating Rust projects (CLI, web services, libraries). Covers workspace structure, error handling, async patterns, and idiomatic Rust best practices.
Rust Project Architecture
Core Principles
- Ownership-first — Embrace borrow checker, no unnecessary clones
- Zero-cost abstractions — Newtype, iterators, async/await
- Workspace for scale — Use Cargo workspace for multi-crate projects
- Error precision — thiserror for libs, anyhow for apps
- Async with Tokio — Tokio runtime + tracing for observability
- No backwards compatibility — Delete, don't deprecate. Change directly
- LiteLLM for LLM APIs — Use LiteLLM proxy for all LLM integrations
No Backwards Compatibility
Delete unused code. Change directly. No compatibility layers.
// ❌ BAD: Deprecated attribute kept around
#[deprecated(since = "0.2.0", note = "Use new_function instead")]
pub fn old_function() { ... }
// ❌ BAD: Type alias for renamed types
pub type OldName = NewName; // "for backwards compatibility"
// ❌ BAD: Unused parameters
fn process(_legacy: &str, data: &Data) { ... }
// ❌ BAD: Feature flags for old behavior
#[cfg(feature = "legacy")]
fn old_impl() { ... }
// ✅ GOOD: Just delete and update all usages
pub fn new_function() { ... }
// Then: Find & replace all old_function → new_function
// ✅ GOOD: Remove unused parameters entirely
fn process(data: &Data) { ... }
LiteLLM for LLM APIs
Use LiteLLM proxy. Don't call provider APIs directly.
// src/llm.rs
use async_openai::{Client, config::OpenAIConfig};
pub fn create_client(base_url: &str, api_key: &str) -> Client<OpenAIConfig> {
let config = OpenAIConfig::new()
.with_api_base(base_url) // LiteLLM proxy URL
.with_api_key(api_key);
Client::with_config(config)
}
// Usage: connect to LiteLLM, use any model
let client = create_client("http://localhost:4000", &api_key);
let request = CreateChatCompletionRequestArgs::default()
.model("gpt-4o") // or "claude-3-opus", "gemini-pro", etc.
.messages(vec![...])
.build()?;
Quick Start
1. Initialize Project
# Simple project
cargo new myapp
cd myapp
# Workspace project
mkdir myapp && cd myapp
cargo init --name app
2. Apply Tech Stack
| Layer | Recommendation |
|---|---|
| Async Runtime | Tokio |
| Web Framework | Axum |
| Serialization | Serde |
| ORM / Database | SeaORM (async, Active Record) |
| CLI | Clap (derive) |
| Error (lib) | thiserror |
| Error (app) | anyhow |
| Logging | tracing + tracing-subscriber |
| HTTP Client | reqwest |
| Config | config-rs |
Web Framework Selection
| Framework | Choose When |
|---|---|
| Axum (default) | Modern microservices, Tokio ecosystem, container deployment, Tower middleware |
| Actix Web | Maximum throughput, WebSocket-heavy, mature ecosystem needed |
| Rocket | Rapid prototyping, small teams, minimal boilerplate |
Axum provides the best balance of performance, ergonomics, and Tokio integration for most projects.
Database / ORM Selection
| Library | Choose When |
|---|---|
| SeaORM (default) | CRUD-heavy services, rapid development, async-first, cross-database testing |
| SQLx | Raw SQL control, maximum performance, compile-time SQL validation |
| Diesel | Compile-time type safety, stable schema, synchronous workloads |
SeaORM is recommended for its Active Record ergonomics, native async support, and seamless Axum integration.
Version Strategy
Always use latest. Never pin in templates.
[dependencies]
tokio = { version = "*", features = ["full"] }
axum = "*"
serde = { version = "*", features = ["derive"] }
# cargo update fetches latest compatible versions
# Cargo.lock ensures reproducible builds
3. Choose Project Structure
Simple Project (Single Crate)
myapp/
├── Cargo.toml
├── src/
│ ├── main.rs # Entry point
│ ├── lib.rs # Library root (optional)
│ ├── config.rs # Configuration
│ ├── error.rs # Error types
│ ├── handlers/ # HTTP handlers (web)
│ │ └── mod.rs
│ ├── services/ # Business logic
│ │ └── mod.rs
│ └── models/ # Domain types
│ └── mod.rs
├── tests/ # Integration tests
│ └── api_test.rs
└── benches/ # Benchmarks
└── bench.rs
Workspace Project (Multi-Crate)
myapp/
├── Cargo.toml # Workspace manifest
├── crates/
│ ├── app/ # Binary crate
│ │ ├── Cargo.toml
│ │ └── src/main.rs
│ ├── core/ # Business logic lib
│ │ ├── Cargo.toml
│ │ └── src/lib.rs
│ └── infra/ # Infrastructure lib
│ ├── Cargo.toml
│ └── src/lib.rs
├── config/
│ └── default.toml
└── Makefile
Architecture Layers
main.rs — Entry Point
Wire dependencies, start runtime. No business logic.
// src/main.rs
use anyhow::Result;
use sea_orm::Database;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<()> {
// Initialize tracing
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer())
.init();
// Load config
let config = myapp::config::load()?;
// Connect to database (SeaORM)
let db = Database::connect(&config.database_url).await?;
// Build application state
let state = myapp::AppState::new(db);
// Build router
let app = myapp::router::build(state);
// Run server
let listener = tokio::net::TcpListener::bind(&config.listen_addr).await?;
tracing::info!("listening on {}", config.listen_addr);
axum::serve(listener, app).await?;
Ok(())
}
lib.rs — Library Root
Re-export public API, define AppState.
// src/lib.rs
pub mod config;
pub mod db;
pub mod error;
pub mod handlers;
pub mod models; // SeaORM entities
pub mod router;
pub mod services;
use sea_orm::DatabaseConnection;
use std::sync::Arc;
pub struct AppState {
pub db: DatabaseConnection,
}
impl AppState {
pub fn new(db: DatabaseConnection) -> Arc<Self> {
Arc::new(Self { db })
}
}
error.rs — Error Handling
// src/error.rs
use axum::{http::StatusCode, response::{IntoResponse, Response}, Json};
use sea_orm::DbErr;
use serde_json::json;
#[derive(Debug, thiserror::Error)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("validation error: {0}")]
Validation(String),
#[error("unauthorized")]
Unauthorized,
#[error("internal error")]
Internal(#[from] anyhow::Error),
#[error("database error: {0}")]
Database(#[from] DbErr),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "unauthorized".into()),
AppError::Internal(_) | AppError::Database(_) => {
tracing::error!("Internal error: {:?}", self);
(StatusCode::INTERNAL_SERVER_ERROR, "internal error".into())
}
};
(status, Json(json!({ "error": message }))).into_response()
}
}
pub type Result<T> = std::result::Result<T, AppError>;
handlers/ — HTTP Layer
// src/handlers/user.rs
use axum::{extract::{Path, State}, Json};
use std::sync::Arc;
use crate::{error::Result, models::user, services, AppState};
pub async fn get_user(
State(state): State<Arc<AppState>>,
Path(id): Path<i64>,
) -> Result<Json<user::Model>> {
let user = services::user::find_by_id(&state.db, id).await?;
Ok(Json(user))
}
pub async fn create_user(
State(state): State<Arc<AppState>>,
Json(input): Json<CreateUserInput>,
) -> Result<Json<user::Model>> {
let user = services::user::create(&state.db, input).await?;
Ok(Json(user))
}
services/ — Business Logic
// src/services/user.rs
use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, Set};
use crate::{error::{AppError, Result}, models::user};
pub async fn find_by_id(db: &DatabaseConnection, id: i64) -> Result<user::Model> {
user::Entity::find_by_id(id)
.one(db)
.await?
.ok_or_else(|| AppError::NotFound(format!("user {}", id)))
}
pub async fn create(db: &DatabaseConnection, input: CreateUserInput) -> Result<user::Model> {
let new_user = user::ActiveModel {
email: Set(input.email),
name: Set(input.name),
..Default::default()
};
let user = new_user.insert(db).await?;
Ok(user)
}
// Find with relations
pub async fn find_with_posts(db: &DatabaseConnection, id: i64) -> Result<(user::Model, Vec<post::Model>)> {
user::Entity::find_by_id(id)
.find_with_related(post::Entity)
.all(db)
.await?
.into_iter()
.next()
.ok_or_else(|| AppError::NotFound(format!("user {}", id)))
}
Workspace Configuration
# Cargo.toml (workspace root)
[workspace]
resolver = "3"
members = ["crates/*"]
[workspace.package]
version = "0.1.0"
edition = "2024"
license = "MIT"
[workspace.dependencies]
tokio = { version = "*", features = ["full"] }
axum = "*"
serde = { version = "*", features = ["derive"] }
serde_json = "*"
sea-orm = { version = "*", features = ["sqlx-postgres", "runtime-tokio-native-tls"] }
thiserror = "*"
anyhow = "*"
tracing = "*"
tracing-subscriber = "*"
# crates/app/Cargo.toml
[package]
name = "app"
version.workspace = true
edition.workspace = true
[dependencies]
core.path = "../core"
infra.path = "../infra"
tokio.workspace = true
axum.workspace = true
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
CLI Application
// src/main.rs
use clap::Parser;
use anyhow::Result;
#[derive(Parser)]
#[command(name = "myapp", version, about)]
struct Cli {
/// Input file path
#[arg(short, long)]
input: PathBuf,
/// Output format
#[arg(short, long, default_value = "json")]
format: OutputFormat,
/// Verbose output
#[arg(short, long)]
verbose: bool,
}
#[derive(Clone, clap::ValueEnum)]
enum OutputFormat {
Json,
Yaml,
Text,
}
fn main() -> Result<()> {
let cli = Cli::parse();
if cli.verbose {
tracing_subscriber::fmt::init();
}
// Process input...
Ok(())
}
Testing
// tests/api_test.rs
use axum::{body::Body, http::{Request, StatusCode}};
use tower::ServiceExt;
#[tokio::test]
async fn test_get_user() {
let app = create_test_app().await;
let response = app
.oneshot(
Request::builder()
.uri("/users/1")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
// Unit test with mock
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_email() {
assert!(validate_email("[email protected]").is_ok());
assert!(validate_email("invalid").is_err());
}
}
Makefile
.PHONY: build run test lint check clean
build:
cargo build --release
run:
cargo run
dev:
cargo watch -x run
test:
cargo test
test-coverage:
cargo tarpaulin --out Html
lint:
cargo clippy -- -D warnings
fmt:
cargo fmt
check: fmt lint test
@echo "All checks passed!"
clean:
cargo clean
# Database (SeaORM)
db-migrate:
sea-orm-cli migrate up
db-generate:
sea-orm-cli generate entity -o src/models
db-fresh:
sea-orm-cli migrate fresh
Checklist
## Project Setup
- [ ] Cargo.toml configured
- [ ] Workspace structure (if multi-crate)
- [ ] Edition 2024 / resolver = "3"
## Architecture
- [ ] main.rs: only wiring + startup
- [ ] lib.rs: re-exports + AppState
- [ ] error.rs: thiserror types
- [ ] handlers/ services/ models/ separation
## Quality
- [ ] tracing for logging
- [ ] clippy warnings as errors
- [ ] cargo fmt enforced
- [ ] Tests for critical paths
## CI
- [ ] cargo check
- [ ] cargo clippy
- [ ] cargo test
- [ ] cargo fmt --check
See Also
- reference/architecture.md — Workspace and module patterns
- reference/tech-stack.md — Crate comparisons
- reference/patterns.md — Builder, Newtype, Error patterns
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.