Rust Patterns

affaan-m/ECC/.kiro/skills/rust-patterns

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications.

AI 產生的概覽

指導撰寫、審查與重構符合慣用法的 Rust 程式碼,涵蓋所有權、錯誤處理、trait、並行與 crate 結構。

功能
此技能為符合慣用法的 Rust 開發提供參考指引與程式碼範例。內容涵蓋所有權與借用、使用 Result、thiserror 與 anyhow 的錯誤處理、列舉與窮盡比對、trait 與泛型、迭代器、以 Arc > 為基礎的並行、通道與非同步、unsafe 程式碼規則,以及模組與 crate 組織。它也列出 cargo 工具指令、慣用法速查表與應避免的反模式。
適用情境
適用於撰寫新的 Rust 程式碼、審查或重構現有 Rust 程式碼,或設計 crate 結構與模組配置。適合需要套用或檢查慣用慣例與型別安全實務的工作。
執行需求
不包含指令碼或資源,僅為說明性指示。範例涉及 thiserror、anyhow、tokio、reqwest 等 Rust crate 以及 cargo 指令,但技能本身不執行任何內容。

Rust Development Patterns

Idiomatic Rust patterns and best practices for building safe, performant, and maintainable applications.

When to Use

  • Writing new Rust code
  • Reviewing Rust code
  • Refactoring existing Rust code
  • Designing crate structure and module layout

How It Works

This skill enforces idiomatic Rust conventions across six key areas: ownership and borrowing to prevent data races at compile time, Result/? error propagation with thiserror for libraries and anyhow for applications, enums and exhaustive pattern matching to make illegal states unrepresentable, traits and generics for zero-cost abstraction, safe concurrency via Arc<Mutex<T>>, channels, and async/await, and minimal pub surfaces organized by domain.

Core Principles

1. Ownership and Borrowing

Rust's ownership system prevents data races and memory bugs at compile time.

rust
// Good: Pass references when you don't need ownershipfn process(data: &[u8]) -> usize {    data.len()}
// Good: Take ownership only when you need to store or consumefn store(data: Vec<u8>) -> Record {    Record { payload: data }}
// Bad: Cloning unnecessarily to avoid borrow checkerfn process_bad(data: &Vec<u8>) -> usize {    let cloned = data.clone(); // Wasteful — just borrow    cloned.len()}

Use Cow for Flexible Ownership

rust
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {    if input.contains(' ') {        Cow::Owned(input.replace(' ', "_"))    } else {        Cow::Borrowed(input) // Zero-cost when no mutation needed    }}

Error Handling

Use Result and ? — Never unwrap() in Production

rust
// Good: Propagate errors with contextuse anyhow::{Context, Result};
fn load_config(path: &str) -> Result<Config> {    let content = std::fs::read_to_string(path)        .with_context(|| format!("failed to read config from {path}"))?;    let config: Config = toml::from_str(&content)        .with_context(|| format!("failed to parse config from {path}"))?;    Ok(config)}
// Bad: Panics on errorfn load_config_bad(path: &str) -> Config {    let content = std::fs::read_to_string(path).unwrap(); // Panics!    toml::from_str(&content).unwrap()}

Library Errors with thiserror, Application Errors with anyhow

rust
// Library code: structured, typed errorsuse thiserror::Error;
#[derive(Debug, Error)]pub enum StorageError {    #[error("record not found: {id}")]    NotFound { id: String },    #[error("connection failed")]    Connection(#[from] std::io::Error),    #[error("invalid data: {0}")]    InvalidData(String),}
// Application code: flexible error handlinguse anyhow::{bail, Result};
fn run() -> Result<()> {    let config = load_config("app.toml")?;    if config.workers == 0 {        bail!("worker count must be > 0");    }    Ok(())}

Option Combinators Over Nested Matching

rust
// Good: Combinator chainfn find_user_email(users: &[User], id: u64) -> Option<String> {    users.iter()        .find(|u| u.id == id)        .map(|u| u.email.clone())}
// Bad: Deeply nested matchingfn find_user_email_bad(users: &[User], id: u64) -> Option<String> {    match users.iter().find(|u| u.id == id) {        Some(user) => match &user.email {            email => Some(email.clone()),        },        None => None,    }}

Enums and Pattern Matching

Model States as Enums

rust
// Good: Impossible states are unrepresentableenum ConnectionState {    Disconnected,    Connecting { attempt: u32 },    Connected { session_id: String },    Failed { reason: String, retries: u32 },}
fn handle(state: &ConnectionState) {    match state {        ConnectionState::Disconnected => connect(),        ConnectionState::Connecting { attempt } if *attempt > 3 => abort(),        ConnectionState::Connecting { .. } => wait(),        ConnectionState::Connected { session_id } => use_session(session_id),        ConnectionState::Failed { retries, .. } if *retries < 5 => retry(),        ConnectionState::Failed { reason, .. } => log_failure(reason),    }}

Exhaustive Matching — No Catch-All for Business Logic

rust
// Good: Handle every variant explicitlymatch command {    Command::Start => start_service(),    Command::Stop => stop_service(),    Command::Restart => restart_service(),    // Adding a new variant forces handling here}
// Bad: Wildcard hides new variantsmatch command {    Command::Start => start_service(),    _ => {} // Silently ignores Stop, Restart, and future variants}

Traits and Generics

Accept Generics, Return Concrete Types

rust
// Good: Generic input, concrete outputfn read_all(reader: &mut impl Read) -> std::io::Result<Vec<u8>> {    let mut buf = Vec::new();    reader.read_to_end(&mut buf)?;    Ok(buf)}
// Good: Trait bounds for multiple constraintsfn process<T: Display + Send + 'static>(item: T) -> String {    format!("processed: {item}")}

Trait Objects for Dynamic Dispatch

rust
// Use when you need heterogeneous collections or plugin systemstrait Handler: Send + Sync {    fn handle(&self, request: &Request) -> Response;}
struct Router {    handlers: Vec<Box<dyn Handler>>,}
// Use generics when you need performance (monomorphization)fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {    handler.handle(request)}

Newtype Pattern for Type Safety

rust
// Good: Distinct types prevent mixing up argumentsstruct UserId(u64);struct OrderId(u64);
fn get_order(user: UserId, order: OrderId) -> Result<Order> {    // Can't accidentally swap user and order IDs    todo!()}
// Bad: Easy to swap argumentsfn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {    todo!()}

Structs and Data Modeling

Builder Pattern for Complex Construction

rust
struct ServerConfig {    host: String,    port: u16,    max_connections: usize,}
impl ServerConfig {    fn builder(host: impl Into<String>, port: u16) -> ServerConfigBuilder {        ServerConfigBuilder { host: host.into(), port, max_connections: 100 }    }}
struct ServerConfigBuilder { host: String, port: u16, max_connections: usize }
impl ServerConfigBuilder {    fn max_connections(mut self, n: usize) -> Self { self.max_connections = n; self }    fn build(self) -> ServerConfig {        ServerConfig { host: self.host, port: self.port, max_connections: self.max_connections }    }}
// Usage: ServerConfig::builder("localhost", 8080).max_connections(200).build()

Iterators and Closures

Prefer Iterator Chains Over Manual Loops

rust
// Good: Declarative, lazy, composablelet active_emails: Vec<String> = users.iter()    .filter(|u| u.is_active)    .map(|u| u.email.clone())    .collect();
// Bad: Imperative accumulationlet mut active_emails = Vec::new();for user in &users {    if user.is_active {        active_emails.push(user.email.clone());    }}

Use collect() with Type Annotation

rust
// Collect into different typeslet names: Vec<_> = items.iter().map(|i| &i.name).collect();let lookup: HashMap<_, _> = items.iter().map(|i| (i.id, i)).collect();let combined: String = parts.iter().copied().collect();
// Collect Results — short-circuits on first errorlet parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

Concurrency

Arc<Mutex<T>> for Shared Mutable State

rust
use std::sync::{Arc, Mutex};
let counter = Arc::new(Mutex::new(0));let handles: Vec<_> = (0..10).map(|_| {    let counter = Arc::clone(&counter);    std::thread::spawn(move || {        let mut num = counter.lock().expect("mutex poisoned");        *num += 1;    })}).collect();
for handle in handles {    handle.join().expect("worker thread panicked");}

Channels for Message Passing

rust
use std::sync::mpsc;
let (tx, rx) = mpsc::sync_channel(16); // Bounded channel with backpressure
for i in 0..5 {    let tx = tx.clone();    std::thread::spawn(move || {        tx.send(format!("message {i}")).expect("receiver disconnected");    });}drop(tx); // Close sender so rx iterator terminates
for msg in rx {    println!("{msg}");}

Async with Tokio

rust
use tokio::time::Duration;
async fn fetch_with_timeout(url: &str) -> Result<String> {    let response = tokio::time::timeout(        Duration::from_secs(5),        reqwest::get(url),    )    .await    .context("request timed out")?    .context("request failed")?;
    response.text().await.context("failed to read body")}
// Spawn concurrent tasksasync fn fetch_all(urls: Vec<String>) -> Vec<Result<String>> {    let handles: Vec<_> = urls.into_iter()        .map(|url| tokio::spawn(async move {            fetch_with_timeout(&url).await        }))        .collect();
    let mut results = Vec::with_capacity(handles.len());    for handle in handles {        results.push(handle.await.unwrap_or_else(|e| panic!("spawned task panicked: {e}")));    }    results}

Unsafe Code

When Unsafe Is Acceptable

rust
// Acceptable: FFI boundary with documented invariants/// # Safety/// `ptr` must be a valid, aligned pointer to an initialized `Widget`.unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &'a Widget {    // SAFETY: caller guarantees ptr is valid and aligned    unsafe { &*ptr }}
// Acceptable: Performance-critical path with proof of correctness// SAFETY: index is always < len due to the loop boundunsafe { slice.get_unchecked(index) }

When Unsafe Is NOT Acceptable

rust
// Bad: Using unsafe to bypass borrow checker// Bad: Using unsafe for convenience// Bad: Using unsafe without a Safety comment// Bad: Transmuting between unrelated types

Module System and Crate Structure

Organize by Domain, Not by Type

text
my_app/├── src/│   ├── main.rs│   ├── lib.rs│   ├── auth/          # Domain module│   │   ├── mod.rs│   │   ├── token.rs│   │   └── middleware.rs│   ├── orders/        # Domain module│   │   ├── mod.rs│   │   ├── model.rs│   │   └── service.rs│   └── db/            # Infrastructure│       ├── mod.rs│       └── pool.rs├── tests/             # Integration tests├── benches/           # Benchmarks└── Cargo.toml

Visibility — Expose Minimally

rust
// Good: pub(crate) for internal sharingpub(crate) fn validate_input(input: &str) -> bool {    !input.is_empty()}
// Good: Re-export public API from lib.rspub mod auth;pub use auth::AuthMiddleware;
// Bad: Making everything pubpub fn internal_helper() {} // Should be pub(crate) or private

Tooling Integration

Essential Commands

bash
# Build and checkcargo buildcargo check              # Fast type checking without codegencargo clippy             # Lints and suggestionscargo fmt                # Format code
# Testingcargo testcargo test -- --nocapture    # Show println outputcargo test --lib             # Unit tests onlycargo test --test integration # Integration tests only
# Dependenciescargo audit              # Security auditcargo tree               # Dependency treecargo update             # Update dependencies
# Performancecargo bench              # Run benchmarks

Quick Reference: Rust Idioms

IdiomDescription
Borrow, don't clonePass &T instead of cloning unless ownership is needed
Make illegal states unrepresentableUse enums to model valid states only
? over unwrap()Propagate errors, never panic in library/production code
Parse, don't validateConvert unstructured data to typed structs at the boundary
Newtype for type safetyWrap primitives in newtypes to prevent argument swaps
Prefer iterators over loopsDeclarative chains are clearer and often faster
#[must_use] on ResultsEnsure callers handle return values
Cow for flexible ownershipAvoid allocations when borrowing suffices
Exhaustive matchingNo wildcard _ for business-critical enums
Minimal pub surfaceUse pub(crate) for internal APIs

Anti-Patterns to Avoid

rust
// Bad: .unwrap() in production codelet value = map.get("key").unwrap();
// Bad: .clone() to satisfy borrow checker without understanding whylet data = expensive_data.clone();process(&original, &data);
// Bad: Using String when &str sufficesfn greet(name: String) { /* should be &str */ }
// Bad: Box<dyn Error> in libraries (use thiserror instead)fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }
// Bad: Ignoring must_use warningslet _ = validate(input); // Silently discarding a Result
// Bad: Blocking in async contextasync fn bad_async() {    std::thread::sleep(Duration::from_secs(1)); // Blocks the executor!    // Use: tokio::time::sleep(Duration::from_secs(1)).await;}

Remember: If it compiles, it's probably correct — but only if you avoid unwrap(), minimize unsafe, and let the type system work for you.

來源與署名

來源:affaan-m/ECC位於.kiro/skills/rust-patterns提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架