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에서 보기