---
name: rust-patrones
description: >
  Patrones idiomaticos de Rust: newtype, builder con typestate, From/Into,
  RAII con Drop, iteradores custom, smart pointers y interior mutability.
  Cargar cuando se diseñe arquitectura Rust o se refactorice código existente.
version: "1.0.0"
herramientasPermitidas: [Read, Glob, Grep]
exclusiones:
  - "No cargar para implementar servicios Rust, APIs HTTP o lógica concurrente — para implementación cargar `rust-experto`."
  - "No cargar para escribir tests Rust — para testing cargar `rust-testing`."
  - "No cargar para errores de compilación Rust — para build errors cargar `build-errors-rust`."
  - "No cargar para patrones de diseño en otros lenguajes — newtype y typestate son patrones idiomáticos de Rust no directamente aplicables en lenguajes con GC."
evolvable: true  # default para skill estandar
---
# Rust Patrones — Diseño Idiomático Rust

Cubre los patrones que producen código Rust seguro, expresivo y mantenible:
newtype para tipos fuertes, builder con typestate, conversiones con From/Into,
RAII con Drop, iteradores custom y uso correcto de smart pointers.

## Cuándo cargar este skill

Invoca `Skill("rust-patrones")` cuando:

- Se diseñen tipos de dominio en Rust (IDs, cantidades, valores monetarios)
- Se implemente el patrón builder para structs con muchos campos opcionales
- Se revise código con clonaciones excesivas o unsafe innecesario
- Se diseñen iteradores custom o adaptadores de iterador
- Se elija entre Box, Rc, Arc, Cell y RefCell

## Cuándo NO cargar

- La pregunta es sobre implementar servicios Rust, APIs HTTP o async con Tokio — para implementación cargar `rust-experto`.
- La tarea es escribir tests Rust — cargar `rust-testing`.
- Los errores son de compilación Rust (borrow checker, lifetime errors) — cargar `build-errors-rust`.
- Los patrones buscados son de otro lenguaje — newtype y typestate son específicos de Rust; en lenguajes con GC los patrones equivalentes son distintos.

## Conceptos clave

### Newtype Pattern

Envuelve un tipo primitivo en un struct de un campo (`struct UserId(Uuid)`).
Aporta type safety en compilación sin costo en runtime. El compilador rechaza
pasar un `UserId` donde se espera un `OrderId`, aunque ambos sean `Uuid` internamente.

### Typestate Pattern

Codifica el estado válido de un objeto en el tipo, no en campos de estado.
Las transiciones inválidas son errores de compilación, no de runtime.

### RAII (Resource Acquisition Is Initialization)

El trait `Drop` garantiza que los recursos se liberan cuando el dueño sale de
scope, incluso en presencia de panic. Conexiones, archivos y locks se cierran
automáticamente.

## Reglas obligatorias

### Newtype para todo ID de dominio — nunca Uuid crudo como parámetro

Pasar `Uuid` directamente permite confundir IDs de distintas entidades. Un
newtype con `#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]` y conversión
desde Uuid cuesta cero en runtime.

```rust
// MAL — nada impide pasar client_id donde se espera order_id
fn transferir(desde: Uuid, hacia: Uuid, monto: Decimal) { ... }

// BIEN — el compilador rechaza el orden equivocado
fn transferir(desde: CuentaId, hacia: CuentaId, monto: Dinero) { ... }
```

### Implementar From — NUNCA conversiones con unwrap en el sitio de uso

`impl From<A> for B` permite `B::from(a)` y habilita el operador `?` para
`Into`. Centraliza la lógica de conversión y elimina conversiones repetidas.

### Cow<str> para funciones que a veces alocan y a veces no

`Cow<'a, str>` retorna una referencia cuando el input no necesita modificarse
y un String cuando sí. Evita alocar siempre o cambiar la firma según el caso.

### RefCell solo en contextos single-thread — Mutex en multi-thread

`RefCell` paniquea en runtime si hay más de un borrow mutable simultáneo.
Solo es correcto en código single-thread (sin `Send`). En multi-thread usar `Mutex`.

## Patrones recomendados

### Newtype con conversiones

```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PedidoId(Uuid);

impl PedidoId {
    pub fn nuevo() -> Self { Self(Uuid::new_v4()) }
    pub fn inner(self) -> Uuid { self.0 }
}

impl From<Uuid> for PedidoId {
    fn from(id: Uuid) -> Self { Self(id) }
}

impl fmt::Display for PedidoId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}
```

### Builder con typestate

```rust
pub struct Borrador;
pub struct Listo;

pub struct PedidoBuilder<Estado> {
    cliente_id: Option<ClienteId>,
    items: Vec<ItemPedido>,
    _estado: PhantomData<Estado>,
}

impl PedidoBuilder<Borrador> {
    pub fn nuevo() -> Self {
        Self { cliente_id: None, items: vec![], _estado: PhantomData }
    }

    pub fn cliente(mut self, id: ClienteId) -> Self {
        self.cliente_id = Some(id);
        self
    }

    pub fn item(mut self, item: ItemPedido) -> Self {
        self.items.push(item);
        self
    }

    // Solo disponible cuando hay cliente Y al menos un item
    pub fn listo(self) -> Result<PedidoBuilder<Listo>, BuildError> {
        if self.cliente_id.is_none() { return Err(BuildError::SinCliente); }
        if self.items.is_empty() { return Err(BuildError::SinItems); }
        Ok(PedidoBuilder { cliente_id: self.cliente_id, items: self.items, _estado: PhantomData })
    }
}

impl PedidoBuilder<Listo> {
    pub fn construir(self) -> Pedido {
        Pedido::new(self.cliente_id.unwrap(), self.items) // safe: typestate garantiza que existe
    }
}
```

### From/Into para conversiones de dominio

```rust
impl From<PedidoRow> for Pedido {
    fn from(row: PedidoRow) -> Self {
        Pedido {
            id: PedidoId::from(row.id),
            cliente_id: ClienteId::from(row.cliente_id),
            total: row.total,
            estado: Estado::from(row.estado_str.as_str()),
        }
    }
}

// Uso con operador ? en contextos que retornan Result<_, ConversionError>
impl TryFrom<&str> for Estado {
    type Error = ParseEstadoError;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        match s {
            "borrador"    => Ok(Estado::Borrador),
            "confirmado"  => Ok(Estado::Confirmado),
            "cancelado"   => Ok(Estado::Cancelado),
            other => Err(ParseEstadoError(other.to_string())),
        }
    }
}
```

### RAII con Drop para recursos

```rust
pub struct ConexionBD {
    conn: Option<PgConnection>,
    pool: Arc<Pool>,
}

impl Drop for ConexionBD {
    fn drop(&mut self) {
        if let Some(conn) = self.conn.take() {
            self.pool.devolver(conn); // se ejecuta incluso en panic
        }
    }
}
```

### Iterador custom

```rust
pub struct PaginaIter<'a> {
    items: &'a [Pedido],
    pos: usize,
    tamano_pagina: usize,
}

impl<'a> Iterator for PaginaIter<'a> {
    type Item = &'a [Pedido];

    fn next(&mut self) -> Option<Self::Item> {
        if self.pos >= self.items.len() { return None; }
        let fin = (self.pos + self.tamano_pagina).min(self.items.len());
        let pagina = &self.items[self.pos..fin];
        self.pos = fin;
        Some(pagina)
    }
}
```

### Cow para funciones mixtas

```rust
use std::borrow::Cow;

fn normalizar_email(email: &str) -> Cow<str> {
    if email.chars().any(|c| c.is_uppercase()) {
        Cow::Owned(email.to_lowercase())  // aloca solo cuando es necesario
    } else {
        Cow::Borrowed(email)              // referencia directa sin aloca
    }
}
```

## Anti-patrones conocidos

### Stringly-typed — strings para conceptos con dominio cerrado

```rust
// MAL — cualquier string es valido en compilacion
fn cambiar_estado(pedido: &mut Pedido, estado: &str) {
    pedido.estado = estado.to_string(); // "activo", "Activo", "ACTIVO"... todos pasan
}

// BIEN — enum exhaustivo
fn cambiar_estado(pedido: &mut Pedido, estado: EstadoPedido) { ... }
```

### Clone excesivo para satisfacer al borrow checker

```rust
// MAL — clonar solo para evitar mover
let nombre = usuario.nombre.clone();
let respuesta = construir_respuesta(nombre);
enviar_log(usuario.nombre.clone()); // ya podria haber usado &usuario.nombre

// BIEN — restructurar para minimizar clones
enviar_log(&usuario.nombre);
let respuesta = construir_respuesta(&usuario.nombre);
```

### Ignorar Drop al usar ManuallyDrop

```rust
// MAL — ManuallyDrop sin plan de liberacion
let valor = ManuallyDrop::new(recurso_importante);
// recurso_importante nunca se libera si no se llama ManuallyDrop::drop

// BIEN — Solo ManuallyDrop cuando se transfiere ownership a FFI con
// documentacion explicita de quien es responsable de liberar
```

## Checklist de verificación

- [ ] Cada ID de dominio es un newtype, no un Uuid/u64 crudo
- [ ] Builder pattern con typestate cuando hay precondiciones de construcción
- [ ] From/Into implementado para conversiones entre capas (row -> domain)
- [ ] TryFrom para conversiones que pueden fallar, no unwrap en el sitio
- [ ] Drop implementado para structs que manejan recursos externos
- [ ] Cow<str> en funciones que a veces modifican y a veces no
- [ ] RefCell solo en single-thread; Mutex en multi-thread
- [ ] Sin unsafe sin comentario explicando invariante que lo justifica

## Gotchas / Errores comunes no obvios

**Newtype que implementa `Deref<Target=T>` puede confundirse con `T` en inferencia de tipos**: si `struct UserId(Uuid)` implementa `Deref<Target=Uuid>`, el compilador puede inferir el tipo `Uuid` en contextos donde se espera `UserId`, anulando la protección del newtype. Fix: no implementar `Deref` en newtypes de dominio cuyo propósito es distinguir tipos; si se necesita acceso al inner, exponer un método `fn inner(&self) -> &Uuid`.

**Builder con typestate no compila con `Box<dyn Builder<State>>` para builders almacenados dinámicamente**: el typestate usa tipos genéricos como phantom data, lo que hace que diferentes estados del builder sean tipos distintos que no tienen un ancestro común en el type system. Fix: si se necesita almacenar builders dinámicamente, usar un enum en lugar de typestate, o aceptar que el builder con typestate solo funciona en contextos donde el tipo completo es conocido en compile time.

**`From<T> for U` no implementa automáticamente `TryFrom<T> for U`**: si se define `From<RawRow> for Entidad`, esto da `Into<Entidad>` automáticamente, pero `TryFrom` no se infiere — hay que implementarlo por separado si la conversión puede fallar. Causa: `From` es infallible por definición. Fix: implementar `TryFrom<RawRow> for Entidad` con error tipado cuando la conversión puede fallar, y usar `From` solo cuando la conversión es garantizada.

**`impl Iterator for MiTipo` con `type Item = &'a T` requiere lifetime explícito en la implementación**: si el iterador retorna referencias a datos que viven en la colección que itera, el lifetime debe ser explícito tanto en la struct del iterador como en el `impl`. El compilador puede sugerir lifetimes incorrectos en mensajes de error. Fix: anotar el lifetime de la referencia al dato origen en la struct del iterador: `struct Iter<'a, T> { inner: &'a [T], pos: usize }`.

---
*Skill creado con swl:crear-skill el 2026-03-31. Versión 1.0.0.*
