Patrones de diseño en Rust con ejemplos: nivel avanzado
Rust es un lenguaje que cambia el enfoque del diseño. Los patrones clásicos de GoF a menudo requieren adaptación debido al estricto sistema de propiedad, tiempos de vida y la ausencia de herencia. En este artículo analizaremos patrones de diseño avanzados que no solo funcionan en Rust, sino que aprovechan sus fortalezas: seguridad de memoria, abstracciones de costo cero y un potente sistema de tipos.
1. Patrón Builder con verificaciones en tiempo de compilación
El Builder clásico en Rust generalmente se implementa mediante una estructura con campos opcionales. A nivel avanzado, podemos usar el sistema de tipos para verificar los campos obligatorios en tiempo de compilación. Esto elimina los errores en tiempo de ejecución de "olvidé establecer un campo obligatorio".
La implementación mediante Type State (estado de tipo) permite que el Builder sea seguro y autodocumentado.
// Estados para Builderstruct Empty;struct WithName(String);struct WithAge(u8);struct Ready(WithName, WithAge);
struct PersonBuilder<State> { state: State,}
impl PersonBuilder<Empty> { pub fn new() -> Self { PersonBuilder { state: Empty } } pub fn name(self, name: &str) -> PersonBuilder<WithName> { PersonBuilder { state: WithName(name.to_string()), } }}
impl PersonBuilder<WithName> { pub fn age(self, age: u8) -> PersonBuilder<Ready> { PersonBuilder { state: Ready(self.state, WithAge(age)), } }}
impl PersonBuilder<Ready> { pub fn build(self) -> Person { Person { name: self.state.0 .0, age: self.state.1 .0, } }}
struct Person { name: String, age: u8,}
fn main() { // Se compila - todos los campos están establecidos let person = PersonBuilder::new() .name("Alice") .age(30) .build(); // No se compilará: falta name() // let person = PersonBuilder::new().age(30).build(); println!("{} is {} years old", person.name, person.age);}Ventaja: los errores de configuración se detectan en tiempo de compilación, no durante la ejecución. Esto es especialmente valioso para objetos complejos con decenas de parámetros.
2. Patrón RAII y punteros inteligentes para gestión de recursos
RAII (Resource Acquisition Is Initialization) no es solo un patrón, sino una filosofía de Rust. Cada recurso (memoria, archivo, socket, bloqueo) debe ser adquirido en el constructor y liberado en el destructor. Rust implementa esto mediante los traits Drop y el sistema de propiedad.
Ejemplo avanzado: creación de un wrapper RAII para un mutex con liberación automática.
use std::ops::Deref;use std::sync::{Arc, Mutex};
struct Guard<T> { data: T,}
impl<T> Drop for Guard<T> { fn drop(&mut self) { println!("¡Recurso liberado!"); }}
impl<T> Deref for Guard<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.data }}
struct ResourceManager<T> { resource: Arc<Mutex<T>>,}
impl<T> ResourceManager<T> { fn new(data: T) -> Self { ResourceManager { resource: Arc::new(Mutex::new(data)), } } fn acquire(&self) -> Guard<std::sync::MutexGuard<T>> { let guard = self.resource.lock().unwrap(); Guard { data: guard } }}
fn main() { let manager = ResourceManager::new(42); { let guard = manager.acquire(); println!("Valor: {}", *guard); // guard sale del ámbito -> libera automáticamente el mutex } // Imprime "¡Recurso liberado!" println!("El mutex está disponible nuevamente");}Idea clave: el destructor se