Inyección de Dependencias (Dependency Injection) en Rust para principiantes

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: ¿Por qué es importante Dependency Injection en Rust?

\n\n

Al escribir programas en Rust, tarde o temprano te encontrarás con una situación en la que un módulo (por ejemplo, un servicio o controlador) necesita usar las capacidades de otro módulo (base de datos, API, registrador). La forma más simple es crear el objeto necesario directamente dentro del código que lo utiliza:

\n\n
struct UserService;\n\nimpl UserService {\n    fn get_user(&self, id: u32) -> String {\n        let db = Database::new(); // ❌ Acoplamiento rígido\n        db.query_user(id)\n    }\n}\n
\n\n

Este enfoque crea un acoplamiento rígido (tight coupling). El código se vuelve difícil de probar, reutilizar y modificar. Dependency Injection (DI) es un patrón en el que las dependencias se pasan al objeto desde el exterior, en lugar de crearse dentro de él. Rust, al ser un lenguaje con un sistema de propiedad y traits, ofrece formas elegantes de implementar DI sin frameworks.

\n\n

En este artículo, analizaremos cómo inyectar dependencias en Rust con ejemplos prácticos: desde la simple transferencia de estructuras hasta el uso de traits y punteros inteligentes.

\n\n

1. Enfoque básico: Pasar dependencias a través del constructor

\n\n

La forma más sencilla de inyectar una dependencia en Rust es pasarla como argumento al constructor o a la función. Consideremos un ejemplo con un registrador.

\n\n

Ejemplo incorrecto (sin DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // ❌ Vinculación rígida a un tipo específico\n}\n\nimpl OrderService {\n    fn new() -> Self {\n        Self {\n            logger: ConsoleLogger, // Se crea internamente\n        }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Pedido: {}", item));\n    }\n}\n
\n\n

Ejemplo correcto (con DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // La dependencia se pasa desde el exterior\n}\n\nimpl OrderService {\n    // El constructor acepta la dependencia\n    fn new(logger: ConsoleLogger) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Pedido: {}", item));\n    }\n}\n\nfn main() {\n    let logger = ConsoleLogger;\n    let service = OrderService::new(logger);\n    service.place_order("Libro sobre Rust");\n}\n
\n\n

¿Qué cambió? Ahora OrderService no crea el registrador por sí mismo. Lo recibe ya listo. Esto ya es DI, pero hay un matiz: todavía estamos vinculados al tipo específico ConsoleLogger. Para hacer el código flexible, necesitamos usar traits.

\n\n

2. Uso de traits para la abstracción de dependencias

\n\n

Los traits en Rust son el equivalente a las interfaces en otros lenguajes. Permiten definir un contrato que las dependencias deben cumplir.

\n\n

Definimos el trait Logger

\n\n
trait Logger {\n    fn log(&self, msg: &str);\n}\n\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[Consola]: {}", msg);\n    }\n}\n\nstruct FileLogger {\n    filename: String,\n}\nimpl Logger for FileLogger {\n    fn log(&self, msg: &str) {\n        // Simulación de escritura en archivo\n        println!("[Archivo {}]: {}", self.filename, msg);\n    }\n}\n
\n\n

Inyectamos el trait mediante genéricos (generics)

\n\n
struct OrderService<L: Logger> {\n    logger: L, // Tipo genérico que implementa el trait Logger\n}\n\nimpl<L: Logger> OrderService<L> {\n    fn new(logger: L) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Pedido: {}", item));\n    }\n}\n\nfn main() {\n    let console_logge                

Blogs

Book Recommendations