Contêiner de Injeção de Dependência para Rust: visão geral da biblioteca dicy
No mundo da programação de sistemas, o Rust é famoso por seu desempenho e segurança de memória. No entanto, ao construir aplicações de grande porte, os desenvolvedores frequentemente enfrentam o problema de gerenciar dependências. A ligação manual de componentes leva a "código rígido", dificulta os testes e reduz a flexibilidade. A solução é um contêiner de Injeção de Dependência (DI), que automatiza a criação e injeção de dependências. Neste artigo, veremos a biblioteca dicy — um dos contêineres DI mais maduros para Rust.
O que é Injeção de Dependência e por que é necessária no Rust?
Injeção de Dependência é um padrão no qual um objeto recebe suas dependências de fora, em vez de criá-las ele mesmo. No Rust, isso é especialmente relevante devido ao sistema de tipos estrito e à propriedade (ownership). O contêiner DI assume:
- Criação de instâncias de serviços (singleton, transient, scoped).
- Resolução do grafo de dependências (injeção recursiva).
- Gerenciamento do tempo de vida dos objetos.
- Possibilidade de substituir implementações durante testes (objetos mock).
Biblioteca dicy: visão geral dos recursos
dicy é um contêiner DI leve, porém poderoso, escrito em Rust puro sem código unsafe. Ele suporta:
- Resolução automática de dependências através de construtores ou fábricas.
- Vários modos de tempo de vida: Singleton, Transient, Scoped (para actix-web, axum e outros frameworks).
- Fábricas assíncronas — criação de serviços usando async/await.
- Injeção via traits — vinculação de um trait a uma implementação específica.
- Contêineres aninhados (para isolamento de módulos).
Instalação e configuração básica
Adicione dicy ao Cargo.toml:
[dependencies]dicy = "0.6"Exemplo 1: Injeção simples via construtor
Vamos criar dois serviços: Config (singleton) e 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 } }}
// Define o módulo com o 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);
// Obtém Database com Config injetado automaticamente let db: Database = container.resolve().unwrap(); println!("Conexão ao BD: {}", db.config.url);}Exemplo 2: Fábricas assíncronas e dependências Scoped
Para aplicações web, é frequentemente necessário criar serviços com escopo de requisição HTTP (scoped). O dicy suporta isso:
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