Rust Patterns

作者 affaan-mef648e01899b无许可证275K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3天前更新

Patrones idiomáticos de Rust, ownership, manejo de errores, traits, concurrencia y buenas prácticas para construir aplicaciones seguras y eficientes.

AI 生成的概览

指导地道的 Rust 开发:所有权、错误处理、trait、并发与 crate 结构。

功能
该技能提供地道的 Rust 模式与最佳实践,用于编写安全、高效、可维护的代码。内容涵盖所有权与借用、使用 Result、thiserror 和 anyhow 的错误处理、枚举与穷尽式模式匹配、trait 与泛型、迭代器、使用 Arc > 的并发、通道与异步、unsafe 代码规则,以及模块与 crate 组织。它还列出常用 cargo 命令、惯用法速查表和应避免的反模式。
适用场景
适用于编写新的 Rust 代码、审查 Rust 代码、重构现有 Rust 代码,或设计 crate 结构与模块组织时。
运行要求
除智能体本身外无需任何脚本或工具,仅为说明性指令。示例中提及 thiserror、anyhow、tokio、reqwest 等 Rust crate 以及 cargo 命令,但阅读这些指导无需安装任何内容。

Patrones de Desarrollo Rust

Patrones idiomáticos y buenas prácticas de Rust para construir aplicaciones seguras, eficientes y mantenibles.

Cuándo Usar

  • Escribir código Rust nuevo
  • Revisar código Rust
  • Refactorizar código Rust existente
  • Diseñar la estructura de crates y la organización de módulos

Cómo Funciona

Este skill refuerza las convenciones idiomáticas de Rust en seis áreas clave: ownership y borrowing para prevenir data races en tiempo de compilación, propagación de errores con Result/? usando thiserror para bibliotecas y anyhow para aplicaciones, enums y pattern matching exhaustivo para hacer imposibles los estados inválidos, traits y genéricos para abstracciones de costo cero, concurrencia segura con Arc<Mutex<T>>, canales y async/await, y superficies pub mínimas organizadas por dominio.

Principios Fundamentales

1. Ownership y Borrowing

El sistema de ownership de Rust previene data races y bugs de memoria en tiempo de compilación.

rust
// Bien: Pasar referencias cuando no se necesita el ownershipfn process(data: &[u8]) -> usize {    data.len()}
// Bien: Tomar ownership solo cuando se necesita almacenar o consumirfn store(data: Vec<u8>) -> Record {    Record { payload: data }}
// Mal: Clonar innecesariamente para evitar el borrow checkerfn process_bad(data: &Vec<u8>) -> usize {    let cloned = data.clone(); // Costoso — solo tomar prestado    cloned.len()}

Usar Cow para Ownership Flexible

rust
use std::borrow::Cow;
fn normalize(input: &str) -> Cow<'_, str> {    if input.contains(' ') {        Cow::Owned(input.replace(' ', "_"))    } else {        Cow::Borrowed(input) // Costo cero cuando no se necesita mutación    }}

Manejo de Errores

Usar Result y ? — Nunca unwrap() en Producción

rust
// Bien: Propagar errores con contextouse 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)}
// Mal: Causa panic en caso de errorfn load_config_bad(path: &str) -> Config {    let content = std::fs::read_to_string(path).unwrap(); // ¡Panic!    toml::from_str(&content).unwrap()}

Errores de Biblioteca con thiserror, Errores de Aplicación con anyhow

rust
// Código de biblioteca: errores estructurados y tipadosuse 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),}
// Código de aplicación: manejo de errores flexibleuse anyhow::{bail, Result};
fn run() -> Result<()> {    let config = load_config("app.toml")?;    if config.workers == 0 {        bail!("worker count must be > 0");    }    Ok(())}

Combinadores de Option en Lugar de Matching Anidado

rust
// Bien: Cadena de combinadoresfn find_user_email(users: &[User], id: u64) -> Option<String> {    users.iter()        .find(|u| u.id == id)        .map(|u| u.email.clone())}
// Mal: Matching profundamente anidadofn 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 y Pattern Matching

Modelar Estados con Enums

rust
// Bien: Los estados imposibles son irrepresentablesenum 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),    }}

Matching Exhaustivo — Sin Comodín en Lógica de Negocio

rust
// Bien: Manejar cada variante explícitamentematch command {    Command::Start => start_service(),    Command::Stop => stop_service(),    Command::Restart => restart_service(),    // Agregar una nueva variante fuerza su manejo aquí}
// Mal: El comodín oculta nuevas variantesmatch command {    Command::Start => start_service(),    _ => {} // Ignora silenciosamente Stop, Restart y variantes futuras}

Traits y Genéricos

Aceptar Genéricos, Retornar Tipos Concretos

rust
// Bien: Entrada genérica, salida concretafn read_all(reader: &mut impl Read) -> std::io::Result<Vec<u8>> {    let mut buf = Vec::new();    reader.read_to_end(&mut buf)?;    Ok(buf)}
// Bien: Bounds de traits para múltiples restriccionesfn process<T: Display + Send + 'static>(item: T) -> String {    format!("processed: {item}")}

Trait Objects para Dispatch Dinámico

rust
// Usar cuando se necesitan colecciones heterogéneas o sistemas de pluginstrait Handler: Send + Sync {    fn handle(&self, request: &Request) -> Response;}
struct Router {    handlers: Vec<Box<dyn Handler>>,}
// Usar genéricos cuando se necesita rendimiento (monomorfización)fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {    handler.handle(request)}

Patrón Newtype para Seguridad de Tipos

rust
// Bien: Tipos distintos previenen mezclar argumentosstruct UserId(u64);struct OrderId(u64);
fn get_order(user: UserId, order: OrderId) -> Result<Order> {    // No se pueden intercambiar accidentalmente user ID y order ID    todo!()}
// Mal: Fácil intercambiar argumentosfn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {    todo!()}

Structs y Modelado de Datos

Patrón Builder para Construcción Compleja

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 }    }}
// Uso: ServerConfig::builder("localhost", 8080).max_connections(200).build()

Iteradores y Closures

Preferir Cadenas de Iteradores sobre Bucles Manuales

rust
// Bien: Declarativo, lazy, composablelet active_emails: Vec<String> = users.iter()    .filter(|u| u.is_active)    .map(|u| u.email.clone())    .collect();
// Mal: Acumulación imperativalet mut active_emails = Vec::new();for user in &users {    if user.is_active {        active_emails.push(user.email.clone());    }}

Usar collect() con Anotación de Tipo

rust
// Recolectar en diferentes tiposlet 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();
// Recolectar Results — cortocircuita al primer errorlet parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

Concurrencia

Arc<Mutex<T>> para Estado Mutable Compartido

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");}

Canales para Paso de Mensajes

rust
use std::sync::mpsc;
let (tx, rx) = mpsc::sync_channel(16); // Canal acotado con backpressure
for i in 0..5 {    let tx = tx.clone();    std::thread::spawn(move || {        tx.send(format!("message {i}")).expect("receiver disconnected");    });}drop(tx); // Cerrar el sender para que el iterador rx termine
for msg in rx {    println!("{msg}");}

Async con 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")}
// Lanzar tareas concurrentesasync 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}

Código Unsafe

Cuándo Unsafe Es Aceptable

rust
// Aceptable: Frontera FFI con invariantes documentados (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: el llamador garantiza que ptr es válido y alineado    unsafe { &*ptr }}
// Aceptable: Ruta crítica de rendimiento con prueba de corrección// SAFETY: index is always < len due to the loop boundunsafe { slice.get_unchecked(index) }

Cuándo Unsafe NO Es Aceptable

rust
// Mal: Usar unsafe para evadir el borrow checker// Mal: Usar unsafe por conveniencia// Mal: Usar unsafe sin un comentario Safety// Mal: Hacer transmute entre tipos no relacionados

Sistema de Módulos y Estructura de Crates

Organizar por Dominio, No por Tipo

text
my_app/├── src/│   ├── main.rs│   ├── lib.rs│   ├── auth/          # Módulo de dominio│   │   ├── mod.rs│   │   ├── token.rs│   │   └── middleware.rs│   ├── orders/        # Módulo de dominio│   │   ├── mod.rs│   │   ├── model.rs│   │   └── service.rs│   └── db/            # Infraestructura│       ├── mod.rs│       └── pool.rs├── tests/             # Pruebas de integración├── benches/           # Benchmarks└── Cargo.toml

Visibilidad — Exponer el Mínimo

rust
// Bien: pub(crate) para compartir internamentepub(crate) fn validate_input(input: &str) -> bool {    !input.is_empty()}
// Bien: Re-exportar la API pública desde lib.rspub mod auth;pub use auth::AuthMiddleware;
// Mal: Hacer todo pubpub fn internal_helper() {} // Debería ser pub(crate) o privado

Integración con Herramientas

Comandos Esenciales

bash
# Construir y verificarcargo buildcargo check              # Verificación de tipos rápida sin codegencargo clippy             # Lints y sugerenciascargo fmt                # Formatear código
# Pruebascargo testcargo test -- --nocapture    # Mostrar salida de printlncargo test --lib             # Solo pruebas unitariascargo test --test integration # Solo pruebas de integración
# Dependenciascargo audit              # Auditoría de seguridadcargo tree               # Árbol de dependenciascargo update             # Actualizar dependencias
# Rendimientocargo bench              # Ejecutar benchmarks

Referencia Rápida: Modismos Rust

ModismoDescripción
Tomar prestado, no clonarPasar &T en lugar de clonar a menos que se necesite el ownership
Hacer estados ilegales irrepresentablesUsar enums para modelar solo estados válidos
? en lugar de unwrap()Propagar errores, nunca causar panic en biblioteca/producción
Parsear, no validarConvertir datos no estructurados a structs tipados en la frontera
Newtype para seguridad de tiposEnvolver primitivos en newtypes para prevenir intercambio de argumentos
Preferir iteradores sobre buclesLas cadenas declarativas son más claras y frecuentemente más rápidas
#[must_use] en ResultsAsegurar que los llamadores manejen los valores de retorno
Cow para ownership flexibleEvitar asignaciones cuando el borrowing es suficiente
Matching exhaustivoSin comodín _ para enums críticos de negocio
Superficie pub mínimaUsar pub(crate) para APIs internas

Anti-Patrones a Evitar

rust
// Mal: .unwrap() en código de producciónlet value = map.get("key").unwrap();
// Mal: .clone() para satisfacer el borrow checker sin entender por quélet data = expensive_data.clone();process(&original, &data);
// Mal: Usar String cuando &str es suficientefn greet(name: String) { /* debería ser &str */ }
// Mal: Box<dyn Error> en bibliotecas (usar thiserror en su lugar)fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }
// Mal: Ignorar advertencias must_uselet _ = validate(input); // Descartando silenciosamente un Result
// Mal: Bloquear en contexto asyncasync fn bad_async() {    std::thread::sleep(Duration::from_secs(1)); // ¡Bloquea el executor!    // Usar: tokio::time::sleep(Duration::from_secs(1)).await;}

Recuerda: Si compila, probablemente es correcto — pero solo si evitas unwrap(), minimizas unsafe y dejas que el sistema de tipos trabaje para ti.

来源与署名

来源:affaan-m/ecc位于docs/es/skills/rust-patterns提交ef648e0

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架