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
آخر نشاط في المصدر
٦ مايو ٢٠٢٦ في ٠٩:٤٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
١٨٠
التفرعات
٢١

خيارات التثبيت

يُحدَّد 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