Patrones de diseño en Rust: de la teoría a la práctica
Los patrones de diseño son soluciones probadas para problemas típicos en el desarrollo de software. En Rust, gracias a su sistema único de propiedad (ownership) y la ausencia de POO clásica, muchos patrones clásicos adquieren un nuevo significado. En este artículo, analizaremos cuatro patrones clave: Singleton, Factory, Builder y Strategy, adaptados al Rust idiomático.
1. Singleton en Rust
En Rust no existen variables estáticas globales en el sentido habitual. Sin embargo, Singleton se puede implementar mediante lazy_static o OnceCell. Veamos un ejemplo con std::sync::OnceLock (disponible desde Rust 1.70).
Implementación con OnceLock
use std::sync::OnceLock;
static CONFIG: OnceLock<Config> = OnceLock::new();
#[derive(Debug)]struct Config { db_url: String, port: u16,}
fn get_config() -> &'static Config { CONFIG.get_or_init(|| { Config { db_url: String::from("postgres://localhost"), port: 5432, } })}
fn main() { let cfg = get_config(); println!("{:?}", cfg);}Punto clave: OnceLock garantiza una inicialización única y seguridad en concurrencia. Esta es la forma idiomática para Rust.
2. Factory mediante traits
El patrón Factory en Rust se implementa excelentemente con traits. No necesitamos clases abstractas, basta con definir una interfaz mediante un trait.
Ejemplo: creación de vehículos
trait Transport { fn deliver(&self);}
struct Truck;impl Transport for Truck { fn deliver(&self) { println!("Entrega en camión por tierra"); }}
struct Ship;impl Transport for Ship { fn deliver(&self) { println!("Entrega en barco por mar"); }}
struct LogisticsFactory;
impl LogisticsFactory { fn create_transport(route_type: &str) -> Box<dyn Transport> { match route_type { "land" => Box::new(Truck), "sea" => Box::new(Ship), _ => panic!("Tipo de ruta desconocido"), } }}
fn main() { let transport = LogisticsFactory::create_transport("sea"); transport.deliver();}Ventaja: Rust no requiere herencia; el polimorfismo se logra mediante traits y Box<dyn Trait>.
3. Builder con cadena tipificada
Builder en Rust se usa ampliamente, especialmente para estructuras con muchos parámetros opcionales. Lo implementamos con una cadena tipificada de métodos que devuelven &mut Self.
Ejemplo: creación de una solicitud HTTP
#[derive(Debug, Default)]struct HttpRequest { url: String, method: String, headers: Vec<(String, String)>, body: Option<String>,}
struct HttpRequestBuilder { request: HttpRequest,}
impl HttpRequestBuilder { fn new(url: &str) -> Self { HttpRequestBuilder { request: HttpRequest { url: url.to_string(), method: "GET".to_string(), ..Default::default() }, } }
fn method(&mut self, method: &str) -> &mut Self { self.request.method = method.to_string(); self }
fn header(&mut self, key: &str, value: &str) -> &mut Self { self.request.headers.push((key.to_string(), value.to_string())); self }
fn body(&mut self, body: &str) -> &mut Self { self.request.body = Some(body.to_string()); self }
fn build(&self) -> HttpRequest { // Aquí se puede agregar validación HttpRequest { ..self.request.clone() } }}
fn ma