Injeção de Dependência em Rust: para iniciantes

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução à Injeção de Dependência em Rust



Quando você escreve programas em Rust, mais cedo ou mais tarde se depara com o problema: como tornar o código flexível, testável e com baixo acoplamento? A resposta é Injeção de Dependência (DI). Este é um padrão de projeto no qual um objeto recebe suas dependências de fora, em vez de criá-las ele mesmo.



Neste artigo, vamos analisar como implementar Injeção de Dependência em Rust com exemplos práticos. Você aprenderá a usar trait-objetos e generics para criar uma arquitetura flexível. O material é voltado para desenvolvedores Rust iniciantes, familiarizados com os fundamentos da linguagem.



Por que a Injeção de Dependência é necessária?



Imagine que você está escrevendo um aplicativo para enviar notificações. Sem DI, a classe NotificationService estará rigidamente ligada a uma implementação específica (por exemplo, EmailSender). Isso torna o código:

  • Difícil de testar — é impossível substituir o serviço real por um objeto mock.
  • Difícil de estender — adicionar um novo método de envio (SMS, Push) exige alteração do código existente.
  • Fortemente acoplado — mudanças em uma parte quebram a outra.


A Injeção de Dependência resolve esses problemas passando as dependências através do construtor, método ou parâmetro. Rust, sendo uma linguagem de sistema com tipagem forte, oferece maneiras elegantes de implementar DI.



Abordagens principais para DI em Rust



Em Rust, existem duas maneiras principais de implementar injeção de dependências: através de trait-objetos (despacho dinâmico) e através de generics (despacho estático). Vamos analisar ambas as abordagens.



1. DI via trait-objetos (despacho dinâmico)



Esta abordagem usa polimorfismo em tempo de execução. Você define um trait e então passa Box<dyn Trait> ou uma referência &dyn Trait.



Exemplo: serviço de logging



// Definindo um trait para loggingtrait Logger {    fn log(&self, message: &str);}

// Implementação para saída no consolestruct ConsoleLogger;

impl Logger for ConsoleLogger { fn log(&self, message: &str) { println!("[LOG]: {}", message); }}

// Implementação para arquivostruct FileLogger { filename: String,}

impl Logger for FileLogger { fn log(&self, message: &str) { // Escrita simplificada em arquivo std::fs::write(&self.filename, message).unwrap(); }}

// Serviço usando DIstruct UserService { logger: Box<dyn Logger>,}

impl UserService { // Injeção de dependência via construtor fn new(logger: Box<dyn Logger>) -> Self { UserService { logger } }

fn create_user(&self, name: &str) { self.logger.log(&format!("Usuário criado: {}", name)); // Lógica de criação de usuário... }}

fn main() { // Usando ConsoleLogger let console_logger = Box::new(ConsoleLogger); let service = UserService::new(console_logger); service.create_user("Alice");

// Usando FileLogger let file_logger = Box::new(FileLogger { filename: "log.txt".into() }); let service2 = UserService::new(file_logger); service2.create_user("Bob");}


Vantagens: flexibilidade, é possível mudar a implementação em tempo de execução. Desvantagens: pequena sobrecarga devido a chamadas virtuais.



2. DI via generics (despacho estático)



Aqui, as dependências são definidas em tempo de compilação através de parâmetros de tipo. Esta é uma abordagem mais performática, mas menos flexível.



Exemplo: o mesmo serviço de logging com generics



trait Logger {    fn log(&self, message: &str);}

struct ConsoleLogge

Blogs

Book Recommendations