Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications.
.claude/skills/affaan-m-rust-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 242% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 167% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 202% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 319% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 240% | 0% |
构建安全、高性能且可维护应用程序的惯用 Rust 模式和最佳实践。
此技能在六个关键领域强制执行惯用的 Rust 约定:所有权和借用,用于在编译时防止数据竞争;Result/? 错误传播,库使用 thiserror 而应用程序使用 anyhow;枚举和穷尽模式匹配,使非法状态无法表示;用于零成本抽象的 trait 和泛型;通过 Arc<Mutex<T>>、通道和 async/await 实现的安全并发;以及按领域组织的最小化 pub 接口。
Rust 的所有权系统在编译时防止数据竞争和内存错误。
rust// Good: Pass references when you don't need ownership fn process(data: &[u8]) -> usize { data.len() } // Good: Take ownership only when you need to store or consume fn store(data: Vec<u8>) -> Record { Record { payload: data } } // Bad: Cloning unnecessarily to avoid borrow checker fn process_bad(data: &Vec<u8>) -> usize { let cloned = data.clone(); // Wasteful — just borrow cloned.len() }
Cow 实现灵活的所有权rustuse 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 } }
Result 和 ? —— 切勿在生产环境中使用 unwrap()rust// Good: Propagate errors with context use 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 error fn load_config_bad(path: &str) -> Config { let content = std::fs::read_to_string(path).unwrap(); // Panics! toml::from_str(&content).unwrap() }
thiserror,应用程序错误使用 anyhowrust// Library code: structured, typed errors use 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 handling use anyhow::{bail, Result}; fn run() -> Result<()> { let config = load_config("app.toml")?; if config.workers == 0 { bail!("worker count must be > 0"); } Ok(()) }
Option 组合子而非嵌套匹配rust// Good: Combinator chain fn find_user_email(users: &[User], id: u64) -> Option<String> { users.iter() .find(|u| u.id == id) .map(|u| u.email.clone()) } // Bad: Deeply nested matching fn 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, } }
rust// Good: Impossible states are unrepresentable enum 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), } }
rust// Good: Handle every variant explicitly match command { Command::Start => start_service(), Command::Stop => stop_service(), Command::Restart => restart_service(), // Adding a new variant forces handling here } // Bad: Wildcard hides new variants match command { Command::Start => start_service(), _ => {} // Silently ignores Stop, Restart, and future variants }
rust// Good: Generic input, concrete output fn 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 constraints fn process<T: Display + Send + 'static>(item: T) -> String { format!("processed: {item}") }
rust// Use when you need heterogeneous collections or plugin systems trait 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) }
rust// Good: Distinct types prevent mixing up arguments struct 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 arguments fn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> { todo!() }
ruststruct 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()
rust// Good: Declarative, lazy, composable let active_emails: Vec<String> = users.iter() .filter(|u| u.is_active) .map(|u| u.email.clone()) .collect(); // Bad: Imperative accumulation let mut active_emails = Vec::new(); for user in &users { if user.is_active { active_emails.push(user.email.clone()); } }
collect()rust// Collect into different types let 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 error let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();
Arc<Mutex<T>> 处理共享可变状态rustuse 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"); }
rustuse 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}"); }
rustuse 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 tasks async 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 }
rust// Acceptable: FFI boundary with documented invariants (Rust 2024+) /// # 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 bound unsafe { slice.get_unchecked(index) }
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
textmy_app/ ├── src/ │ ├── main.rs │ ├── lib.rs │ ├── auth/ # 领域模块 │ │ ├── mod.rs │ │ ├── token.rs │ │ └── middleware.rs │ ├── orders/ # 领域模块 │ │ ├── mod.rs │ │ ├── model.rs │ │ └── service.rs │ └── db/ # 基础设施 │ ├── mod.rs │ └── pool.rs ├── tests/ # 集成测试 ├── benches/ # 基准测试 └── Cargo.toml
rust// Good: pub(crate) for internal sharing pub(crate) fn validate_input(input: &str) -> bool { !input.is_empty() } // Good: Re-export public API from lib.rs pub mod auth; pub use auth::AuthMiddleware; // Bad: Making everything pub pub fn internal_helper() {} // Should be pub(crate) or private
bash# Build and check cargo build cargo check # Fast type checking without codegen cargo clippy # Lints and suggestions cargo fmt # Format code # Testing cargo test cargo test -- --nocapture # Show println output cargo test --lib # Unit tests only cargo test --test integration # Integration tests only # Dependencies cargo audit # Security audit cargo tree # Dependency tree cargo update # Update dependencies # Performance cargo bench # Run benchmarks
| 惯用法 | 描述 | |-------|-------------| | 借用,而非克隆 | 传递 &T,除非需要所有权,否则不要克隆 | | 使非法状态无法表示 | 使用枚举仅对有效状态进行建模 | | ? 优于 unwrap() | 传播错误,切勿在库/生产代码中恐慌 | | 解析,而非验证 | 在边界处将非结构化数据转换为类型化结构体 | | Newtype 用于类型安全 | 将基本类型包装在 newtype 中以防止参数错位 | | 优先使用迭代器而非循环 | 声明式链更清晰且通常更快 | | 对 Result 使用 #[must_use] | 确保调用者处理返回值 | | 使用 Cow 实现灵活的所有权 | 当借用足够时避免分配 | | 穷尽匹配 | 业务关键枚举不使用通配符 _ | | 最小化 pub 接口 | 内部 API 使用 pub(crate) |
rust// Bad: .unwrap() in production code let value = map.get("key").unwrap(); // Bad: .clone() to satisfy borrow checker without understanding why let data = expensive_data.clone(); process(&original, &data); // Bad: Using String when &str suffices fn 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 warnings let _ = validate(input); // Silently discarding a Result // Bad: Blocking in async context async fn bad_async() { std::thread::sleep(Duration::from_secs(1)); // Blocks the executor! // Use: tokio::time::sleep(Duration::from_secs(1)).await; }
请记住:如果它能编译,那它很可能是正确的 —— 但前提是你要避免 unwrap(),最小化 unsafe,并让类型系统为你工作。
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 14,138 | 13,352 | -6% | 1 | 1 | 0% | 3,091 | 6,569 | +113% | 0 | 0 | — |
case-02 | fail→fail | 16,144 | 12,941 | -20% | 1 | 1 | 0% | 3,330 | 6,357 | +91% | 0 | 0 | — |
case-03 | pass→pass | 5,056 | 4,362 | -14% | 1 | 1 | 0% | 1,116 | 4,673 | +319% | 0 | 0 | — |
case-04 | fail→pass | 7,350 | 6,662 | -9% | 1 | 1 | 0% | 1,478 | 5,057 | +242% | 0 | 0 | — |
case-05 | pass→pass | 6,688 | 4,614 | -31% | 1 | 1 | 0% | 1,381 | 4,696 | +240% | 0 | 0 | — |
case-06 | pass→pass | 13,721 | 11,054 | -19% | 1 | 1 | 0% | 2,642 | 5,859 | +122% | 0 | 0 | — |
case-07 | pass→pass | 6,944 | 4,083 | -41% | 1 | 1 | 0% | 1,280 | 4,605 | +260% | 0 | 0 | — |
case-08 | pass→pass | 5,526 | 6,611 | +20% | 1 | 1 | 0% | 1,150 | 5,177 | +350% | 0 | 0 | — |
case-09 | fail→pass | 10,174 | 8,434 | -17% | 1 | 1 | 0% | 2,049 | 5,471 | +167% | 0 | 0 | — |
case-10 | pass→pass | 4,974 | 3,627 | -27% | 1 | 1 | 0% | 955 | 4,483 | +369% | 0 | 0 | — |
case-11 | fail→pass | 7,720 | 7,079 | -8% | 1 | 1 | 0% | 1,750 | 5,290 | +202% | 0 | 0 | — |
case-12 | pass→pass | 4,355 | 4,296 | -1% | 1 | 1 | 0% | 897 | 4,486 | +400% | 0 | 0 | — |
case-13 | pass→pass | 5,684 | 6,132 | +8% | 1 | 1 | 0% | 1,207 | 4,959 | +311% | 0 | 0 | — |
case-14 | pass→pass | 6,688 | 5,814 | -13% | 1 | 1 | 0% | 1,425 | 4,915 | +245% | 0 | 0 | — |
case-15 | pass→pass | 9,005 | 6,705 | -26% | 1 | 1 | 0% | 1,905 | 5,016 | +163% | 0 | 0 | — |
case-16 | pass→pass | 7,914 | 6,394 | -19% | 1 | 1 | 0% | 1,561 | 4,936 | +216% | 0 | 0 | — |
case-17 | pass→pass | 12,088 | 5,830 | -52% | 1 | 1 | 0% | 2,269 | 4,902 | +116% | 0 | 0 | — |
case-18 | pass→pass | 2,796 | 4,858 | +74% | 1 | 1 | 0% | 516 | 4,666 | +804% | 0 | 0 | — |
case-19 | pass→pass | 4,481 | 3,882 | -13% | 1 | 1 | 0% | 863 | 4,515 | +423% | 0 | 0 | — |
case-20 | pass→pass | 7,686 | 6,800 | -12% | 1 | 1 | 0% | 1,639 | 5,328 | +225% | 0 | 0 | — |
case-21 | pass→pass | 6,461 | 5,710 | -12% | 1 | 1 | 0% | 1,343 | 4,962 | +269% | 0 | 0 | — |
case-22 | pass→pass | 20,411 | 15,271 | -25% | 1 | 1 | 0% | 3,875 | 7,028 | +81% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.