Communitygithub.com

patrones-rust

Patrones idiomáticos de Rust, ownership, manejo de errores, traits, concurrencia y mejores prácticas para construir aplicaciones seguras y eficientes. Útil al escribir o revisar código Rust cuando están en juego ownership, manejo de errores, traits o concurrencia.

¿Qué es patrones-rust?

Este skill proporciona una guía completa de patrones y mejores prácticas idiomáticas de Rust, cubriendo seis áreas clave: ownership y borrowing para prevenir condiciones de carrera en tiempo de compilación, manejo de errores con `Result`/`?` usando `thiserror` en bibliotecas y `anyhow` en aplicaciones, enumeraciones y pattern matching exhaustivo que hacen que los estados ilegales sean irrepresentables, traits y genéricos para abstracción de costo cero, concurrencia segura mediante `Arc<Mutex<T>>`, canales y async/await, y superficies `pub` mínimas organizadas por dominio. Es útil para desarrolladores que usan Rust con herramientas como Claude Code, Cursor o Codex, y proporciona ejemplos de código concretos y anti-patrones a evitar. El skill refuerza la filosofía de Rust: si compila, probablemente es correcto, siempre que se eviten `unwrap()`, se minimice `unsafe` y se deje que el sistema de tipos trabaje a favor del programador.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/rust-patterns

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Desarrollo en Rust

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

Cuándo Usarlo

  • 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 impone convenciones idiomáticas de Rust en seis áreas clave: ownership y borrowing para prevenir condiciones de carrera en tiempo de compilación, propagación de errores con Result/? usando thiserror para bibliotecas y anyhow para aplicaciones, enumeraciones y pattern matching exhaustivo para hacer que los estados ilegales sean irrepresentables, traits y genéricos para abstracción de costo cero, concurrencia segura mediante 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 condiciones de carrera y errores de memoria en tiempo de compilación.

// Bien: Pasa referencias cuando no necesitas ownership
fn process(data: &[u8]) -> usize {
    data.len()
}

// Bien: Toma ownership solo cuando necesitas almacenar o consumir
fn store(data: Vec<u8>) -> Record {
    Record { payload: data }
}

// Mal: Clonar innecesariamente para evitar el borrow checker
fn process_bad(data: &Vec<u8>) -> usize {
    let cloned = data.clone(); // Ineficiente — solo presta
    cloned.len()
}

Usa Cow para Ownership Flexible

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

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

// Bien: Propaga errores con contexto
use anyhow::{Context, Result};

fn load_config(path: &str) -> Result<Config> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("fallo al leer configuración desde {path}"))?;
    let config: Config = toml::from_str(&content)
        .with_context(|| format!("fallo al analizar configuración desde {path}"))?;
    Ok(config)
}

// Mal: Paniquea en caso de error
fn load_config_bad(path: &str) -> Config {
    let content = std::fs::read_to_string(path).unwrap(); // ¡Paniquea!
    toml::from_str(&content).unwrap()
}

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

// Código de biblioteca: errores estructurados y tipados
use thiserror::Error;

#[derive(Debug, Error)]
pub enum StorageError {
    #[error("registro no encontrado: {id}")]
    NotFound { id: String },
    #[error("fallo de conexión")]
    Connection(#[from] std::io::Error),
    #[error("datos inválidos: {0}")]
    InvalidData(String),
}

// Código de aplicación: manejo de errores flexible
use anyhow::{bail, Result};

fn run() -> Result<()> {
    let config = load_config("app.toml")?;
    if config.workers == 0 {
        bail!("el número de trabajadores debe ser > 0");
    }
    Ok(())
}

Combinadores de Option en Lugar de Matching Anidado

// Bien: Cadena de combinadores
fn find_user_email(users: &[User], id: u64) -> Option<String> {
    users.iter()
        .find(|u| u.id == id)
        .map(|u| u.email.clone())
}

// Mal: Matching profundamente anidado
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,
    }
}

Enumeraciones y Pattern Matching

Modela Estados como Enums

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

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

// Bien: Maneja cada variante explícitamente
match command {
    Command::Start => start_service(),
    Command::Stop => stop_service(),
    Command::Restart => restart_service(),
    // Agregar una nueva variante fuerza el manejo aquí
}

// Mal: El comodín oculta nuevas variantes
match command {
    Command::Start => start_service(),
    _ => {} // Ignora silenciosamente Stop, Restart y variantes futuras
}

Traits y Genéricos

Acepta Genéricos, Devuelve Tipos Concretos

// Bien: Entrada genérica, salida concreta
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)
}

// Bien: Límites de trait para múltiples restricciones
fn process<T: Display + Send + 'static>(item: T) -> String {
    format!("procesado: {item}")
}

Objetos Trait para Despacho Dinámico

// Úsalos cuando necesites colecciones heterogéneas o sistemas de plugins
trait Handler: Send + Sync {
    fn handle(&self, request: &Request) -> Response;
}

struct Router {
    handlers: Vec<Box<dyn Handler>>,
}

// Usa genéricos cuando necesites rendimiento (monomorfización)
fn fast_process<H: Handler>(handler: &H, request: &Request) -> Response {
    handler.handle(request)
}

Patrón Newtype para Seguridad de Tipos

// Bien: Tipos distintos evitan mezclar argumentos
struct UserId(u64);
struct OrderId(u64);

fn get_order(user: UserId, order: OrderId) -> Result<Order> {
    // No se pueden intercambiar accidentalmente los IDs
    todo!()
}

// Mal: Es fácil intercambiar argumentos
fn get_order_bad(user_id: u64, order_id: u64) -> Result<Order> {
    todo!()
}

Structs y Modelado de Datos

Patrón Builder para Construcción Compleja

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

Prefiere Cadenas de Iteradores sobre Bucles Manuales

// Bien: Declarativo, perezoso, componible
let active_emails: Vec<String> = users.iter()
    .filter(|u| u.is_active)
    .map(|u| u.email.clone())
    .collect();

// Mal: Acumulación imperativa
let mut active_emails = Vec::new();
for user in &users {
    if user.is_active {
        active_emails.push(user.email.clone());
    }
}

Usa collect() con Anotación de Tipo

// Colecciona en diferentes tipos
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();

// Colecciona Results — cortocircuita en el primer error
let parsed: Result<Vec<i32>, _> = strings.iter().map(|s| s.parse()).collect();

Concurrencia

Arc<Mutex<T>> para Estado Compartido Mutable

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 envenenado");
        *num += 1;
    })
}).collect();

for handle in handles {
    handle.join().expect("hilo de trabajo paniqueó");
}

Canales para Paso de Mensajes

use std::sync::mpsc;

let (tx, rx) = mpsc::sync_channel(16); // Canal con límite y contrapresión

for i in 0..5 {
    let tx = tx.clone();
    std::thread::spawn(move || {
        tx.send(format!("mensaje {i}")).expect("receptor desconectado");
    });
}
drop(tx); // Cierra el emisor para que el iterador de rx termine

for msg in rx {
    println!("{msg}");
}

Async con Tokio

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("solicitud agotó el tiempo de espera")?
    .context("solicitud falló")?;

    response.text().await.context("fallo al leer el cuerpo")
}

// Lanza tareas concurrentes
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!("la tarea lanzada paniqueó: {e}")));
    }
    results
}

Código unsafe

Cuándo unsafe es Aceptable

// Aceptable: Límite FFI con invariantes documentados (Rust 2024+)
/// # Seguridad
/// `ptr` debe ser un puntero válido y alineado a un `Widget` inicializado.
unsafe fn widget_from_raw<'a>(ptr: *const Widget) -> &'a Widget {
    // SEGURIDAD: el llamante garantiza que ptr es válido y está alineado
    unsafe { &*ptr }
}

// Aceptable: Ruta crítica de rendimiento con prueba de corrección
// SEGURIDAD: index siempre es < len debido al límite del bucle
unsafe { slice.get_unchecked(index) }

Cuándo unsafe NO es Aceptable

// Mal: Usar unsafe para evadir el borrow checker
// Mal: Usar unsafe por conveniencia
// Mal: Usar unsafe sin un comentario de Seguridad
// Mal: Transmutar entre tipos no relacionados

Sistema de Módulos y Estructura de Crate

Organiza por Dominio, No por Tipo

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 — Expón Mínimamente

// Bien: pub(crate) para compartir internamente
pub(crate) fn validate_input(input: &str) -> bool {
    !input.is_empty()
}

// Bien: Re-exporta la API pública desde lib.rs
pub mod auth;
pub use auth::AuthMiddleware;

// Mal: Hacer todo pub
pub fn internal_helper() {} // Debería ser pub(crate) o privada

Integración con Herramientas

Comandos Esenciales

# Compilar y verificar
cargo build
cargo check              # Verificación de tipos rápida sin generación de código
cargo clippy             # Lints y sugerencias
cargo fmt                # Formatear código

# Pruebas
cargo test
cargo test -- --nocapture    # Mostrar salida de println
cargo test --lib             # Solo pruebas unitarias
cargo test --test integration # Solo pruebas de integración

# Dependencias
cargo audit              # Auditoría de seguridad
cargo tree               # Árbol de dependencias
cargo update             # Actualizar dependencias

# Rendimiento
cargo bench              # Ejecutar benchmarks

Referencia Rápida: Modismos de Rust

ModismoDescripción
Presta, no clonesPasa &T en lugar de clonar a menos que se necesite ownership
Haz que los estados ilegales sean irrepresentablesUsa enums para modelar solo estados válidos
? sobre unwrap()Propaga errores, nunca paniquees en bibliotecas/código de producción
Analiza, no validesConvierte datos no estructurados a structs tipados en el límite
Newtype para seguridad de tiposEnvuelve primitivos en newtypes para evitar intercambios de argumentos
Prefiere iteradores sobre buclesLas cadenas declarativas son más claras y a menudo más rápidas
#[must_use] en ResultsAsegura que los llamantes manejen los valores de retorno
Cow para ownership flexibleEvita asignaciones cuando basta con prestar
Matching exhaustivoSin comodín _ para enums críticos de negocio
Superficie pub mínimaUsa pub(crate) para APIs internas

Anti-patrones a Evitar

// Mal: .unwrap() en código de producción
let value = map.get("key").unwrap();

// Mal: .clone() para satisfacer al borrow checker sin entender por qué
let data = expensive_data.clone();
process(&original, &data);

// Mal: Usar String cuando &str es suficiente
fn greet(name: String) { /* debería ser &str */ }

// Mal: Box<dyn Error> en bibliotecas (usa thiserror en su lugar)
fn parse(input: &str) -> Result<Data, Box<dyn std::error::Error>> { todo!() }

// Mal: Ignorar advertencias de must_use
let _ = validate(input); // Descarta silenciosamente un Result

// Mal: Bloquear en contexto async
async fn bad_async() {
    std::thread::sleep(Duration::from_secs(1)); // ¡Bloquea el executor!
    // Usa: 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.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills relacionados