Introdução: Princípios SOLID em Rust — por que são necessários?
SOLID são cinco princípios fundamentais da programação orientada a objetos que ajudam a criar código flexível, sustentável e escalável. Embora Rust não seja uma linguagem OOP clássica (não possui herança de classes da forma tradicional), seu sistema de ownership, traits e generics permite implementar brilhantemente todos os princípios SOLID. Compreender esses princípios no contexto do Rust ajuda a escrever código fácil de testar, refatorar e estender.
Neste artigo, analisaremos cada um dos cinco princípios SOLID com exemplos práticos em Rust. Você verá como trait substitui interfaces, como Box<dyn Trait> ajuda com inversão de dependências e como evitar erros típicos.
1. Princípio da Responsabilidade Única (SRP) em Rust
Single Responsibility Principle (SRP): cada módulo, struct ou função deve ter apenas um motivo para mudar. Em Rust, isso significa que uma struct deve ser responsável por uma única entidade, sem misturar lógica de negócios, manipulação de dados e saída.
Exemplo de violação do SRP
struct User { name: String, email: String,}
impl User { // Mistura lógica de negócios e persistência fn save_to_db(&self) { println!("Salvando {} no banco de dados", self.name); } fn send_email(&self, message: &str) { println!("Enviando email para {}: {}", self.email, message); }}Aqui, a struct User é responsável pelos dados do usuário, pela persistência e pelo envio de emails. Isso viola o SRP.
Implementação correta do SRP
struct User { name: String, email: String,}
struct UserRepository;impl UserRepository { fn save(&self, user: &User) { println!("Salvando {} no banco de dados", user.name); }}
struct EmailService;impl EmailService { fn send(&self, user: &User, message: &str) { println!("Enviando email para {}: {}", user.email, message); }}Agora cada struct tem uma única responsabilidade: User — dados, UserRepository — armazenamento, EmailService — envio. O código ficou mais modular e testável.
2. Princípio Aberto/Fechado (OCP) através de Traits
Open/Closed Principle (OCP): entidades de software devem estar abertas para extensão, mas fechadas para modificação. Em Rust, isso é perfeitamente implementado com trait e dispatch dinâmico.
Exemplo sem OCP
fn calculate_area(shape: &str, params: &[f64]) -> f64 { match shape { "circle" => std::f64::consts::PI * params[0] * params[0], "rectangle" => params[0] * params[1], _ => panic!("Forma desconhecida"), }}Ao adicionar uma nova forma (por exemplo, triângulo), seria necessário modificar a função.
Abordagem correta com OCP
trait Shape { fn area(&self) -> f64;}
struct Circle { radius: f64 }impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }}
struct Rectangle { width: f64, height: f64 }impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
// Função fechada para modificação, mas aberta para extensãofn print_area(shape: &dyn Shape) { println!("Área: {}", shape.area());}Agora, para adicionar um triângulo, basta criar uma nova struct e implementar o trait Shape. A função print_area não requer alterações.
3. Princípio da Substituição de Liskov (LSP) e Herança através de Traits
Liskov Substitution Principle (LSP): objetos em um programa devem ser substituíveis por instâncias de