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.

Quellinformationen

Repository
softspark/ai-toolkit
Letzte Quellaktivität
6. Mai 2026 um 09:48
Erkannte Sprache von SKILL.md
Englisch
Sterne
179
Forks
21

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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)
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen