Introdução à Injeção de Dependência em Rust
Injeção de Dependência (DI) é um padrão de projeto no qual um objeto recebe suas dependências externamente, em vez de criá-las por conta própria. Em Rust, onde segurança e desempenho são prioridades, a DI ajuda a criar código flexível, testável e com baixo acoplamento. Neste artigo, vamos analisar como implementar a injeção de dependências em Rust, usando traits, genéricos e contêineres.
Diferente de linguagens com reflexão (Java, C#), Rust não fornece frameworks DI embutidos. No entanto, seu sistema de tipos e propriedade permite criar soluções elegantes sem perda de desempenho.
Abordagens principais para Injeção de Dependência
Em Rust, existem várias maneiras de implementar DI. Vamos considerar três abordagens principais.
1. Injeção via construtor (Constructor Injection)
A maneira mais simples e comum é passar as dependências no construtor da struct. Isso garante que o objeto esteja sempre em um estado válido.
// Definimos um trait para enviar notificaçõestrait Notifier { fn send(&self, message: &str);}
// Implementação para Emailstruct EmailNotifier { smtp_server: String,}
impl Notifier for EmailNotifier { fn send(&self, message: &str) { println!("Enviando email via {}: {}", self.smtp_server, message); }}
// Implementação para SMSstruct SmsNotifier { api_key: String,}
impl Notifier for SmsNotifier { fn send(&self, message: &str) { println!("Enviando SMS com chave {}: {}", self.api_key, message); }}
// Serviço que usa Notifierstruct NotificationService { notifier: Box<dyn Notifier>,}
impl NotificationService { // Injeção de dependência via construtor fn new(notifier: Box<dyn Notifier>) -> Self { NotificationService { notifier } }
fn notify(&self, message: &str) { self.notifier.send(message); }}
fn main() { let email_notifier = Box::new(EmailNotifier { smtp_server: "smtp.example.com".to_string(), }); let service = NotificationService::new(email_notifier); service.notify("Hello, Rust!");}2. Injeção via genéricos (Generic Injection)
O uso de genéricos permite evitar a despacho dinâmico e aumentar o desempenho por meio da monomorfização.
// Mesmo trait Notifier, mas o serviço agora é genéricostruct NotificationService<T: Notifier> { notifier: T,}
impl<T: Notifier> NotificationService<T> { fn new(notifier: T) -> Self { NotificationService { notifier } }
fn notify(&self, message: &str) { self.notifier.send(message); }}
fn main() { let email_notifier = EmailNotifier { smtp_server: "smtp.example.com".to_string(), }; let service = NotificationService::new(email_notifier); service.notify("Hello from generic Rust!");}3. Injeção via contêiner de dependências
Para projetos complexos, é conveniente usar um contêiner de dependências, que gerencia a criação e o tempo de vida dos objetos. Em Rust, é possível implementar um contêiner simples manualmente.
use std::collections::HashMap;
// Trait para fábricas de dependênciastrait Factory<T> { fn create(&self) -> T;}
// Contêiner de dependências simplesstruct Container { factories: HashMap<String, Box<dyn std::any::Any>>,}
impl Container { fn new() -> Self { Container { factories: HashMap::new(), } }
fn register<T: 'static>(&mut self, name: &str, factory: Box<dyn Factory<T>>) { self.factories.insert(name.to_string(), Box::new(factory)); }
fn resolve<T: 'static>(&self, name: &str) -> Option<T> { self.factories.get(name).and_then(|f|