Contenedor de Inyección de Dependencias para Rust: descripción general de la biblioteca dicy
En el mundo de la programación de sistemas, Rust es famoso por su rendimiento y seguridad de memoria. Sin embargo, al construir aplicaciones grandes, los desarrolladores a menudo se enfrentan al problema de gestionar las dependencias. La vinculación manual de componentes conduce a «código rígido», dificulta las pruebas y reduce la flexibilidad. La solución es un contenedor de Inyección de Dependencias (DI), que automatiza la creación e inyección de dependencias. En este artículo, analizaremos la biblioteca dicy, uno de los contenedores DI más maduros para Rust.
¿Qué es la Inyección de Dependencias y por qué es necesaria en Rust?
La Inyección de Dependencias es un patrón en el que un objeto recibe sus dependencias desde el exterior, en lugar de crearlas él mismo. En Rust, esto es especialmente relevante debido al estricto sistema de tipos y propiedad (ownership). Un contenedor DI se encarga de:
- Creación de instancias de servicios (singleton, transient, scoped).
- Resolución del grafo de dependencias (inyección recursiva).
- Gestión del ciclo de vida de los objetos.
- Posibilidad de sustituir implementaciones durante las pruebas (objetos mock).
Biblioteca dicy: descripción general de capacidades
dicy es un contenedor DI ligero pero potente, escrito en Rust puro sin código unsafe. Soporta:
- Resolución automática de dependencias a través de constructores o fábricas.
- Varios modos de ciclo de vida: Singleton, Transient, Scoped (para actix-web, axum y otros frameworks).
- Fábricas asíncronas — creación de servicios usando async/await.
- Inyección a través de traits — vinculación de un trait a una implementación concreta.
- Contenedores anidados (para aislamiento de módulos).
Instalación y configuración básica
Añada dicy a su Cargo.toml:
[dependencies]dicy = "0.6"Ejemplo 1: Inyección simple por constructor
Creemos dos servicios: Config (singleton) y Database (transient), que depende de Config.
use dicy::{Container, Module, Provide, Singleton, Transient};
#[derive(Clone)]struct Config { url: String,}
impl Config { fn new() -> Self { Config { url: "postgres://localhost".into() } }}
struct Database { config: Config,}
impl Database { fn new(config: Config) -> Self { Database { config } }}
// Definimos el módulo con el registrostruct AppModule;
impl Module for AppModule { fn configure(container: &mut Container) { container .register::<Config>(Singleton::new(Config::new)) .register::<Database>(Transient::new(Database::new)); }}
fn main() { let container = Container::new(); container.install(AppModule);
// Obtenemos Database con Config inyectado automáticamente let db: Database = container.resolve().unwrap(); println!("Conexión a BD: {}", db.config.url);}Ejemplo 2: Fábricas asíncronas y dependencias Scoped
Para aplicaciones web, a menudo es necesario crear servicios con ámbito de solicitud HTTP (scoped). dicy lo soporta:
use dicy::{Container, Module, Scoped, AsyncFactory, Provide};use async_trait::async_trait;
struct RequestContext { user_id: u64,}
#[async_trait]trait UserService { async fn get_name(&self) -> String;}
struct UserServiceImpl { ctx: RequestContext,}
#[async_trait]impl UserService for UserServiceImpl { async fn get_name(&self) -> String { format!("User #{}", self.ctx.user_id) }}
struct WebModule;
impl Module for WebModule { fn configure(container: &mut Container) { container También en la biblioteca
Aprendizaje automático en Rust: revisión de bibliotecas para ML y Data Science
Planificador de tareas Java: Quartz Scheduler — guía completa
Limitación de tasa en C++: bibliotecas y herramientas para control de solicitudes
Cliente Redis para C# (.NET): StackExchange.Redis — conexión y ejemplos
Generación Swagger/OpenAPI para Rust: Utoipa y Aide