Patrones arquitectónicos en Rust: cómo escribir aplicaciones robustas sin dolor

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Por qué la arquitectura en Rust no es un lujo, sino una necesidad



Cuando escribes en Rust, el sistema de tipos y el borrow checker ya te brindan un 80% de protección contra errores. Pero la seguridad de la memoria es solo la base. Si construyes sobre ella una arquitectura caótica, tarde o temprano el proyecto se convertirá en un laberinto de dependencias mutuas, donde cambiar un módulo rompe otros diez.



Los patrones arquitectónicos en Rust no son una cuestión de moda ni una reliquia del mundo de la POO. Son soluciones probadas para problemas típicos: ¿cómo crear un objeto que no viole las invariantes? ¿Cómo conectar componentes para que se puedan probar por separado? ¿Cómo organizar la lógica para que se pueda ampliar sin modificar el código existente?



En este artículo, analizaremos tres grupos de patrones (creacionales, estructurales y de comportamiento) con ejemplos reales en Rust. Sin teoría abstracta, solo código que se puede aplicar de inmediato.



Patrones creacionales: creando objetos correctamente



Builder: el salvador de los constructores con 10 parámetros



En Rust no hay sobrecarga de constructores. Si una estructura tiene más de 3–4 campos, escribir un solo constructor para todos los casos significa condenar al usuario a leer una firma larga con Option y Default. Builder resuelve este problema de manera elegante.



struct ServerConfig {    host: String,    port: u16,    workers: u8,    enable_tls: bool,    max_connections: u32,}

struct ServerConfigBuilder { host: Option<String>, port: Option<u16>, workers: Option<u8>, enable_tls: Option<bool>, max_connections: Option<u32>,}

impl ServerConfigBuilder { fn new() -> Self { Self { host: None, port: None, workers: None, enable_tls: None, max_connections: None, } }

fn host(mut self, host: &str) -> Self { self.host = Some(host.to_string()); self }

fn port(mut self, port: u16) -> Self { self.port = Some(port); self }

fn workers(mut self, workers: u8) -> Self { self.workers = Some(workers); self }

fn enable_tls(mut self, enable: bool) -> Self { self.enable_tls = Some(enable); self }

fn max_connections(mut self, max: u32) -> Self { self.max_connections = Some(max); self }

fn build(self) -> Result<ServerConfig, String> { Ok(ServerConfig { host: self.host.unwrap_or_else(|| "127.0.0.1".to_string()), port: self.port.unwrap_or(8080), workers: self.workers.unwrap_or(4), enable_tls: self.enable_tls.unwrap_or(false), max_connections: self.max_connections.unwrap_or(100), }) }}

// Usolet config = ServerConfigBuilder::new() .host("0.0.0.0") .port(443) .enable_tls(true) .build() .expect("Configuración inválida");


¿Por qué funciona esto en Rust? El patrón Builder se combina perfectamente con el sistema de propiedad: la cadena de métodos consume y devuelve self, y el build final devuelve el objeto listo o un error de validación. Sin referencias mutables ni condiciones de carrera.



Fábrica: cuando el tipo de retorno depende del parámetro



Imagina que necesitas analizar diferentes formatos: JSON, YAML, TOML. En lugar de usar match en cada lugar, utilizamos un método de fábrica.



trait Parser {    fn parse(&self, input: &str) -> Result<serde_json::Value, String>;}

struct JsonParser;impl Parser for JsonParser { fn parse(&self, input: &str) -> Result<serde_json::Value, String> { serde_json::from_str(input).map_err(|e| e.to_string()) }}

struct YamlParser;impl Parser for YamlParser { fn parse(&self, input: &str) -> Result<serde_json::

Blogs

Book Recommendations