Skip to main content

rust-patterns

Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy.

来源信息

仓库
softspark/ai-toolkit
最近来源活动
2026年5月6日 09:48
检测到的 SKILL.md 语言
英语
星标
179
分支
21

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
rust-patterns
description
Rust: ownership, lifetimes, async (Tokio), Result/anyhow/thiserror, traits, unsafe. Triggers: Rust, borrow checker, lifetime, Tokio, cargo, trait, impl, Result, unsafe, clippy.
effort
medium
user-invocable
false
allowed-tools
Read
# Rust Patterns ## Project Structure ``` my-app/ ├── Cargo.toml ├── src/ │ ├── main.rs # Binary entry point │ ├── lib.rs # Library root (re-exports) │ ├── error.rs # Crate-level error types │ ├── api/ │ │ ├── mod.rs │ │ └── handlers.rs │ └── domain/ │ ├── mod.rs │ └── service.rs ├── tests/ # Integration tests (separate crate) │ └── api_test.rs ├── benches/ # criterion benchmarks │ └── throughput.rs └── examples/ └── demo.rs ``` Workspace layout for multi-crate projects: ```toml # Cargo.toml (workspace root) [workspace] resolver = "2" members = ["crates/core", "crates/api", "crates/cli"] [workspace.dependencies] serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } ``` --- ## Idioms / Code Style ### Ownership and Borrowing ```rust // Borrow when you only need to read fn print_name(name: &str) { println!("{name}"); } // Take ownership when storing or consuming the value fn register_user(name: String) -> User { User { name, id: Uuid::new_v4() } } ``` ### Lifetimes ```rust // Annotate only when the compiler cannot infer struct Parser<'input> { source: &'input str, pos: usize, } impl<'input> Parser<'input> { fn next_token(&mut self) -> Option<&'input str> { let start = self.pos; // ... advance self.pos ... Some(&self.source[start..self.pos]) } } ``` ### Trait-Based Design ```rust trait Repository { fn find_by_id(&self, id: Uuid) -> Result<Option<User>, DbError>; fn save(&self, user: &User) -> Result<(), DbError>; } // Accept generics for testability fn create_user(repo: &impl Repository, name: String) -> Result<User, AppError> { let user = User::new(name); repo.save(&user)?; Ok(user) } ``` ### Iterators, Pattern Matching, Newtype ```rust // Iterator chains over manual loops let active: Vec<&str> = users.iter() .filter(|u| u.is_active) .map(|u| u.email.as_str()) .collect(); // Exhaustive matching match command { Command::Start { port } => start_server(port), Command::Stop => shutdown(), } // let-else for early exit (Rust 1.65+) let Some(cfg) = load_config() else { return Ok(Config::default()); }; // Newtype to prevent primitive misuse struct UserId(Uuid); struct Email(String); impl Email { fn new(raw: &str) -> Result<Self, ValidationError> { if raw.contains('@') { Ok(Self(raw.to_lowercase())) } else { Err(ValidationError::InvalidEmail) } } } ``` ### Builder Pattern ```rust #[derive(Default)] struct RequestBuilder { url: String, timeout: Option<Duration> } impl RequestBuilder { fn url(mut self, url: impl Into<String>) -> Self { self.url = url.into(); self } fn timeout(mut self, d: Duration) -> Self { self.timeout = Some(d); self } fn build(self) -> Result<Request, BuildError> { if self.url.is_empty() { return Err(BuildError::MissingUrl); } Ok(Request { url: self.url, timeout: self.timeout.unwrap_or(Duration::from_secs(30)) }) } } ``` --- ## Error Handling ### thiserror (libraries) vs anyhow (binaries) ```rust use thiserror::Error; #[derive(Debug, Error)] pub enum AppError { #[error("not found: {0}")] NotFound(String), #[error("validation failed: {0}")] Validation(String), #[error("database error")] Database(#[from] sqlx::Error), #[error(transparent)] Unexpected(#[from] anyhow::Error), } ``` ```rust // anyhow for application / binary code -- adds context to any error use anyhow::{Context, Result}; fn load_config(path: &Path) -> Result<Config> { let content = fs::read_to_string(path) .with_context(|| format!("failed to read {}", path.display()))?; toml::from_str(&content).context("invalid TOML") } ``` ### Error Propagation and Boundary Mapping ```rust // ? converts and propagates via From impls fn get_email(pool: &PgPool, id: Uuid) -> Result<String, AppError> { let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id) .fetch_optional(pool).await? .ok_or_else(|| AppError::NotFound(format!("user {id}")))?; Ok(user.email) } // Map domain errors to HTTP at the API boundary impl IntoResponse for AppError { fn into_response(self) -> axum::response::Response { let (status, msg) = match &self { AppError::NotFound(m) => (StatusCode::NOT_FOUND, m.clone()), AppError::Validation(m) => (StatusCode::BAD_REQUEST, m.clone()), _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal error".into()), }; (status, Json(json!({ "error": msg }))).into_response() } } ``` --- ## Testing Patterns ### Unit Tests (inline module) ```rust #[cfg(test)] mod tests { use super::*; #[test] fn email_rejects_invalid() { assert!(Email::new("bad").is_err()); } #[tokio::test] async fn fetches_user() { let pool = setup_test_db().await; let user = get_user(&pool, test_id()).await.unwrap(); assert_eq!(user.name, "Alice"); } } ``` ### Integration Tests (tests/ directory) ```rust // tests/api_test.rs -- compiled as separate crate, only sees pub API #[tokio::test] async fn health_returns_200() { let app = my_app::app().await; let resp = app.oneshot( Request::builder().uri("/health").body(Body::empty()).unwrap() ).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); } ``` ### Mocking (mockall) and Property Testing (proptest) ```rust use mockall::automock; #[automock] trait UserRepo { fn find(&self, id: Uuid) -> Result<Option<User>, DbError>; } #[test] fn returns_not_found_when_missing() { let mut mock = MockUserRepo::new(); mock.expect_find().returning(|_| Ok(None)); assert!(matches!(UserService::new(mock).get_user(id), Err(AppError::NotFound(_)))); } ``` ```rust use proptest::prelude::*; proptest! { #[test] fn roundtrips(s in "[a-z]{1,20}@[a-z]{1,10}\\.[a-z]{2,4}") { let e = Email::new(&s).unwrap(); assert_eq!(e.to_string(), s.to_lowercase()); } } ``` ```bash cargo test # All tests cargo test --lib # Unit only cargo test --test api_test # Single integration file cargo nextest run # Parallel runner, better output ``` --- ## Common Libraries | Crate | Purpose | Notes | |-------|---------|-------| | **serde** / serde_json | Serialization | `#[derive(Serialize, Deserialize)]` on DTOs | | **tokio** | Async runtime | `features = ["full"]` for apps | | **axum** | HTTP framework | Tower-based, extractors, `State` | | **clap** | CLI parsing | `#[derive(Parser)]` | | **reqwest** | HTTP client | Async, rustls TLS | | **tracing** | Structured logging | Replaces `log`; use `tracing-subscriber` | | **sqlx** | Async SQL | Compile-time checked queries | | **tower** | Middleware | Layers, timeouts, rate limiting | | **rayon** | Data parallelism | `.par_iter()` drop-in | | **criterion** | Benchmarking | Statistical, HTML reports | ### Axum + Tracing Minimal Server ```rust use axum::{extract::State, routing::get, Json, Router}; use std::sync::Arc; struct AppState { db: PgPool } async fn list_users(State(s): State<Arc<AppState>>) -> Json<Vec<User>> { Json(sqlx::query_as!(User, "SELECT * FROM users") .fetch_all(&s.db).await.unwrap()) } #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .json().init(); let pool = PgPool::connect(&std::env::var("DATABASE_URL").unwrap()).await.unwrap(); let app = Router::new().route("/users", get(list_users)).with_state(Arc::new(AppState { db: pool })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } ``` --- ## Performance Tips ```rust // Avoid cloning -- return references when possible fn names(data: &[Record]) -> Vec<&str> { data.iter().map(|r| r.name.as_str()).collect() } // Cow for conditional ownership -- zero alloc on the happy path use std::borrow::Cow; fn normalize(input: &str) -> Cow<'_, str> { if input.contains(' ') { Cow::Owned(input.replace(' ', "_")) } else { Cow::Borrowed(input) } } // Rayon for CPU-bound parallelism use rayon::prelude::*; let out: Vec<_> = inputs.par_iter().map(|i| compute(i)).collect(); // Preallocate collections let mut v = Vec::with_capacity(items.len()); // Zero-cost abstractions: iterators compile to the same code as manual loops let sum: u64 = values.iter().filter(|v| **v > 0).sum(); // #[inline] only on small hot functions crossing crate boundaries ``` ### Benchmarking (criterion)
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看