Patrones de diseño en Rust a nivel avanzado: ejemplos de código

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

Patrones de diseño en Rust: nivel avanzado con ejemplos

\n\n

Los patrones de diseño son soluciones probadas para problemas típicos que ayudan a crear una arquitectura flexible, mantenible y escalable. En Rust, con su sistema de propiedad, préstamo y traits, muchos patrones clásicos adquieren una implementación única. En este artículo analizaremos patrones de diseño avanzados que son especialmente efectivos en el ecosistema de Rust.

\n\n

1. Builder avanzado (Constructor)

\n\n

El patrón Builder clásico en Rust a menudo se implementa mediante una cadena de métodos que devuelven &mut Self. Sin embargo, a nivel avanzado utilizamos un Builder tipificado con tipos de estado para garantizar la corrección en tiempo de compilación.

\n\n

Problema: control de campos obligatorios

\n

Un Builder normal no garantiza que el usuario haya completado todos los campos obligatorios. Podemos olvidar llamar a .build() o no establecer un parámetro críticamente importante.

\n\n

Solución: Builder con tipos fantasma

\n
use std::marker::PhantomData;\n\n// Tipos de estado para los campos\nstruct UsernameNotSet;\nstruct UsernameSet;\nstruct PasswordNotSet;\nstruct PasswordSet;\n\nstruct UserBuilder<U, P> {\n    username: Option<String>,\n    password: Option<String>,\n    _username: PhantomData<U>,\n    _password: PhantomData<P>,\n}\n\nimpl UserBuilder<UsernameNotSet, PasswordNotSet> {\n    pub fn new() -> Self {\n        Self {\n            username: None,\n            password: None,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl<P> UserBuilder<UsernameNotSet, P> {\n    pub fn username(mut self, name: &str) -> UserBuilder<UsernameSet, P> {\n        self.username = Some(name.to_string());\n        UserBuilder {\n            username: self.username,\n            password: self.password,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl<U> UserBuilder<U, PasswordNotSet> {\n    pub fn password(mut self, pwd: &str) -> UserBuilder<U, PasswordSet> {\n        self.password = Some(pwd.to_string());\n        UserBuilder {\n            username: self.username,\n            password: self.password,\n            _username: PhantomData,\n            _password: PhantomData,\n        }\n    }\n}\n\nimpl UserBuilder<UsernameSet, PasswordSet> {\n    pub fn build(self) -> User {\n        User {\n            username: self.username.expect("Username must be set"),\n            password: self.password.expect("Password must be set"),\n        }\n    }\n}\n\npub struct User {\n    pub username: String,\n    pub password: String,\n}\n\nfn main() {\n    // Ahora el compilador no permitirá llamar a build() sin establecer ambos campos\n    let user = UserBuilder::new()\n        .username("alice")\n        .password("secret123")\n        .build();\n    println!("User: {}", user.username);\n}
\n\n

Ventaja: el error "campo olvidado" se detecta en tiempo de compilación, no en tiempo de ejecución.

\n\n

2. Patrón State (Estado) mediante traits

\n\n

El patrón State permite que un objeto cambie su comportamiento al cambiar su estado interno. En Rust esto se implementa elegantemente mediante enumeraciones (enum) y traits.

\n\n

Implementación con enum

\n
enum ConnectionState {\n    Disconnected,\n    Connecting,\n    Connected,\n}\n\nstruct Connection {\n    state: ConnectionState,\n}\n\nimpl Connection {\n    pub fn new() -> Self {\n        Self { state: ConnectionState::Disconnected }\n    }\n\n    pub fn connect(&mut self) {\n        match self.state {\n            ConnectionState::Disconnected => {\n                println!("Iniciando conexión...");\n                self.state = ConnectionState::Connecting;\n            }\n            ConnectionState::Connecting => {\n                println!("Ya conectando...");\n            }\n            ConnectionState::Connected => {\n                println!("Ya conectado.");\n            }\n        }\n    }\n\n    pub fn disconnect(&mut self) {\n        match self.state {\n            ConnectionState::Connected => {\n                println!("Desconectando...");\n                self.state = ConnectionState::Disconnected;\n            }\n            _ => {\n                println!("No se puede desconectar en este estado.");\n            }\n        }\n    }\n}\n\nfn main() {\n    let mut conn = Connection::new();\n    conn.connect();\n    conn.connect();\n    conn.disconnect();\n}
\n\n

Ventaja: el comportamiento depende del estado actual, y los estados inválidos son imposibles de representar.

Blogs

Book Recommendations