Principios SOLID en Rust: teoría y práctica (nivel avanzado)

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

Introducción: SOLID en el mundo de Rust



Los principios SOLID son la base de una arquitectura flexible y mantenible. Sin embargo, su implementación en Rust tiene sus propias particularidades, debido a su sistema único de propiedad, los traits y la ausencia de POO clásica. En este artículo, analizaremos cómo aplicar cada uno de los cinco principios a nivel avanzado, utilizando Rust idiomático. No solo enumeraremos las reglas, sino que mostraremos cómo se transforman en el contexto del sistema de tipos y el préstamo.



Este artículo está dirigido a desarrolladores que ya conocen los fundamentos de Rust y SOLID. Nos centraremos en los detalles: cómo evitar violar LSP al trabajar con tipos genéricos, cómo ISP afecta el diseño de traits, y por qué DIP en Rust a menudo requiere el uso de objetos trait o Box.



1. Principio de Responsabilidad Única (SRP) y Propiedad



En Rust, el SRP adquiere una dimensión adicional debido a las estrictas reglas de propiedad. Una clase (o estructura) debe tener una sola razón para cambiar. Pero, ¿cómo se relaciona esto con la transferencia de propiedad?



Problema: estructura que gestiona recursos y lógica

Consideremos un ejemplo donde una estructura gestiona simultáneamente un descriptor de archivo y analiza datos. Esto viola el SRP.



use std::fs::File;use std::io::Read;

// ❌ Violación de SRP: la estructura gestiona el recurso y procesa los datosstruct DataProcessor { file: File,}

impl DataProcessor { fn new(path: &str) -> Self { DataProcessor { file: File::open(path).unwrap(), } }

fn process(&mut self) -> Vec<u8> { let mut buffer = Vec::new(); self.file.read_to_end(&mut buffer).unwrap(); // ... lógica de procesamiento compleja buffer }}


Solución: separación mediante transferencia de propiedad

Separamos las responsabilidades: una estructura se encarga de la lectura, otra del procesamiento. La propiedad se transfiere explícitamente.



use std::fs::File;use std::io::Read;

// ✅ Primera responsabilidad: gestión del archivostruct FileReader { file: File,}

impl FileReader { fn new(path: &str) -> Self { FileReader { file: File::open(path).unwrap(), } }

fn read(self) -> Vec<u8> { let mut buffer = Vec::new(); // self.file se consume (moved), ya que después de la lectura no es necesaria self.file.read_to_end(&mut buffer).unwrap(); buffer }}

// ✅ Segunda responsabilidad: procesamiento de datosstruct DataProcessor { data: Vec<u8>,}

impl DataProcessor { fn new(data: Vec<u8>) -> Self { DataProcessor { data } }

fn process(&self) -> Vec<u8> { // Procesamiento inmutable self.data.iter().map(|&b| b + 1).collect() }}

fn main() { let reader = FileReader::new("data.bin"); let raw_data = reader.read(); // reader ya no está disponible let processor = DataProcessor::new(raw_data); let result = processor.process();}


Punto clave: usamos self (propiedad) en lugar de &self en read() para indicar explícitamente que después de la lectura, FileReader ya no es necesario. Esto hace que el flujo de datos sea transparente y previene fugas de recursos.



2. Principio de Abierto/Cerrado (OCP) y Traits



El OCP establece: las entidades deben estar abiertas para la extensión, pero cerradas para la modificación. En Rust, esto se implementa idealmente mediante traits y genéricos sin usar tablas virtuales (vtable), a menos que sea necesario.



Ejemplo avanzado: estrategia con cero sobrecarga

Supongamos que tenemos un sistema de registro. En lugar de modificar el núcleo de la aplicación para cada nuevo backend, usamos polimorfismo a través de traits.



/                

Blogs

Book Recommendations