Rust Development Patterns
Улучшите Claude Code с помощью идиоматических шаблонов Rust, правил владения, безопасной конкурентности и лучших практик обработки ошибок для высокопроизводительных приложений.
Этот навык предоставляет Claude специализированные знания о лучших практиках Rust, начиная от основных принципов владения и заимствования и заканчивая продвинутыми паттернами конкурентности с использованием Tokio. Он гарантирует, что генерируемый или рефакторизуемый код соответствует идиоматическим соглашениям, используя перечисления для представления недопустимых состояний, реализуя надёжную обработку ошибок с помощью anyhow и thiserror, а также организуя проекты с предметно-ориентированной модульной структурой. Это незаменимый помощник для разработчиков, создающих системы, готовые к эксплуатации, где безопасность и производительность имеют первостепенное значение.
Ключевые особенности
Варианты использования
| name | rust-patterns |
|---|---|
| description | Idiomatic Rust patterns, ownership, error handling, traits, concurrency, and best practices for building safe, performant applications. |
| origin | ECC |
Idiomatic Rust patterns and best practices for building safe, performant, and maintainable applications.
- Writing new Rust code
- Reviewing Rust code
- Refactoring existing Rust code
- Designing crate structure and module layout
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.
Rust's ownership system prevents data races and memory bugs at compile time.
// 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()
}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
}
}// 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()
}// 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(())
}// 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,
}
}// 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),
}
}// 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
}// 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}")
}// 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)
}// 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!()
}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()// 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 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();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");
}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}");
}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 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
}// 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) }// Bad: Using unsafe to bypass borrow checker
// Bad: Using unsafe for convenience
// Bad: Using unsafe without a Safety comment
// Bad: Transmuting between unrelated typesmy_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
// 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# 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| Idiom | Description |
|---|---|
| Borrow, don't clone | Pass &T instead of cloning unless ownership is needed |
| Make illegal states unrepresentable | Use enums to model valid states only |
? over unwrap() |
Propagate errors, never panic in library/production code |
| Parse, don't validate | Convert unstructured data to typed structs at the boundary |
| Newtype for type safety | Wrap primitives in newtypes to prevent argument swaps |
| Prefer iterators over loops | Declarative chains are clearer and often faster |
#[must_use] on Results |
Ensure callers handle return values |
Cow for flexible ownership |
Avoid allocations when borrowing suffices |
| Exhaustive matching | No wildcard _ for business-critical enums |
Minimal pub surface |
Use pub(crate) for internal APIs |
// 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;
}Remember: If it compiles, it's probably correct — but only if you avoid unwrap(), minimize unsafe, and let the type system work for you.
✨ Что это
Навык Rust Development Patterns — это свод идиоматических практик и шаблонов для разработки на Rust. Он помогает писать безопасный, производительный и поддерживаемый код, следуя философии языка: ownership, borrowing, traits, enums и concurrency. Подходит как для новых проектов, так и для рефакторинга существующего кода.
⚙️ Как работает
Навык структурирован по шести ключевым областям Rust, каждая со своими правилами и примерами.
🗝️ Владение и заимствование (Ownership & Borrowing)
- Передавайте ссылки (
&T), когда владение не требуется — так компилятор гарантирует отсутствие гонок данных на этапе компиляции. - Используйте
Cow(clone-on-write) для гибкого управления памятью: если данных не меняются — просто заимствуем, если меняются — при необходимости клонируем. - Избегайте клонирования для обхода borrow checker’а — это антипаттерн.
fn normalize(input: &str) -> Cow<'_, str> {
if input.contains(' ') { Cow::Owned(input.replace(' ', "_")) }
else { Cow::Borrowed(input) } // без аллокации
}
🛡️ Обработка ошибок (Error Handling)
- Никогда не вызывайте
unwrap()в production — используйтеResultи оператор?. - Для библиотек используйте
thiserror(структурированные, типизированные ошибки), для приложений —anyhow(гибкие ошибки с контекстом). - Предпочитайте комбинаторы (
map,and_then,filter) вложеннымmatchдляOption.
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)
}
🔷 Перечисления и сопоставление с образцом (Enums & Pattern Matching)
- Моделируйте состояния как enums — «незаконные состояния делайте непредставимыми».
- Сопоставляйте все варианты явно (exhaustive matching) без
_для бизнес-логики — так добавление нового варианта заставит обработать его везде.
🧩 Трейты и обобщения (Traits & Generics)
- Принимайте обобщённые типы (
impl Read,T: Display + Send), возвращайте конкретные типы — это даёт zero-cost абстракцию. - Для разнородных коллекций используйте трейт-объекты (
Box<dyn Handler>), для производительности — обобщения (мономорфизация). - Используйте newtype pattern (
struct UserId(u64)) для защиты от путаницы аргументов одного типа.
🏗️ Структуры и модели данных (Structs & Data Modeling)
- Реализуйте паттерн Builder для сложной инициализации структур с множеством опциональных полей.
- Оганизуйте код по доменам, а не по типам:
auth/,orders/,db/— модули должны отражать бизнес-логику. - Минимизируйте публичный API (
pub(crate)для внутренних функций).
⚡ Итераторы, замыкания, конкурентность (Iterators, Closures & Concurrency)
- Цепочки итераторов (
filter,map,collect) предпочтительнее ручных циклов — они декларативны, ленивы и часто быстрее. - Используйте
Arc<MutexT>для разделяемого изменяемого состояния между потоками, каналы (mpsc) для передачи сообщений (с bounded-буфером для backpressure). - Для асинхронного I/O используйте Tokio:
tokio::spawn,tokio::time::timeout.
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")
}
⚠️ Unsafe-код
- Допустим только на границе FFI (c
Safety-комментарием) или в критических по производительности участках с формальным доказательством корректности. - Недопустим: для обхода borrow checker’а, ради удобства, без комментария безопасности.
📋 Когда использовать
- Написание нового Rust-кода — сразу применяйте идиомы.
- Code review — проверяйте соответствие описанным шаблонам.
- Рефакторинг — заменяйте
unwrap, вложенныеmatch, лишние клонирования. - Проектирование крейта — организуйте модули по доменам, задавайте минимальный
pub-интерфейс.
💡 Важно знать
- «Если компилируется — скорее всего правильно», но только если избегать
unwrap(), минимизироватьunsafeи доверять системе типов. - Для быстрой проверки используйте:
cargo check(быстрая проверка типов),cargo clippy(линтинг),cargo fmt(форматирование),cargo test(тесты). - В библиотеках не используйте
Box<dyn Error>— замените наthiserrorс собственными типами ошибок. - Не игнорируйте предупреждения
#[must_use]— особенно наResult, иначе ошибки будут молча проглочены. - В асинхронном коде не блокируйте executor вызовами
std::thread::sleep— используйтеtokio::time::sleep/async_std::task::sleep.
Как этот навык помогает с безопасностью памяти в Rust?
Он обеспечивает строгие правила владения и заимствования, гарантируя правильное использование ссылок и помогая ИИ избегать распространённых ошибок, таких как ненужное клонирование или unsafe блоки.
Может ли этот навык помочь с асинхронным программированием в Rust?
Да, он предоставляет шаблоны реализации для Tokio runtime, включая синтаксис async/await, порождение задач и безопасное разделение состояния.
Даёт ли он рекомендации по структуре проекта?
Да, он рекомендует модульную организацию на основе предметной области вместо группировки по типам, способствуя лучшей инкапсуляции и управлению поверхностью API.
Какие крейты для обработки ошибок поддерживает этот навык?
Он следует лучшим отраслевым практикам, используя thiserror для структурированных библиотечных ошибок и anyhow для гибкого распространения ошибок на уровне приложений.
Привязан ли этот навык к конкретной версии Rust?
Он сосредоточен на современном, идиоматическом Rust (паттерны Edition 2021 и 2024), гарантируя совместимость кода с последними возможностями компилятора и линтерами.
Синхронизируйте навыки с Claude Cowork, Claude Code, Codex и другими.
Установите одной командой.
npx skillfish add affaan-m/everything-claude-code rust-patternsИсточник: https://mcpmarket.com/tools/skills/rust-development-patterns-4
Комментарии
Комментариев пока нет. Будьте первым.