Princípios SOLID em Rust: teoria e prática do A ao Z

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: Por que o SOLID é importante em Rust?

SOLID são cinco princípios fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e escalável. Apesar de Rust não ser uma linguagem OOP clássica (não possui herança de classes da forma tradicional), esses princípios são aplicáveis aqui graças a traits, structs, genéricos e ownership de memória. Neste artigo, analisaremos cada princípio SOLID com exemplos de código em Rust, mostraremos como implementá-los e explicaremos por que eles tornam seu código melhor.



1. Single Responsibility Principle (SRP): Princípio da Responsabilidade Única

Essência: Cada módulo, struct ou função deve ter apenas um motivo para mudar.

Em Rust, isso é frequentemente violado quando uma única struct é responsável por armazenar dados, serializá-los e pela lógica de negócios. A abordagem correta é separar as responsabilidades.



Exemplo ruim (violação do SRP)

struct User {    name: String,    email: String,}

impl User { fn save_to_db(&self) { // lógica de salvar no banco }

fn send_email(&self, message: &str) { // lógica de enviar email }}

Aqui, User é responsável tanto pelo armazenamento quanto pelo envio — dois motivos para mudar.



Exemplo bom (cumprimento do SRP)

struct User {    name: String,    email: String,}

struct UserRepository;impl UserRepository { fn save(&self, user: &User) { /* ... */ }}

struct EmailService;impl EmailService { fn send(&self, user: &User, message: &str) { /* ... */ }}

Agora cada struct tem uma única responsabilidade: User — dados, UserRepository — salvamento, EmailService — envio.



2. Open-Closed Principle (OCP): Princípio Aberto/Fechado

Essência: Entidades de software devem estar abertas para extensão, mas fechadas para modificação.

Em Rust, isso é alcançado com traits e genéricos. Você pode adicionar novos comportamentos sem alterar o código existente.



Exemplo: Sistema de descontos

Suponha que temos uma função para calcular o preço com desconto. Em vez de adicionar novos tipos de desconto com if-else, usamos um trait:



trait Discount {    fn apply(&self, price: f64) -> f64;}

struct NoDiscount;impl Discount for NoDiscount { fn apply(&self, price: f64) -> f64 { price }}

struct SeasonalDiscount(f64);impl Discount for SeasonalDiscount { fn apply(&self, price: f64) -> f64 { price * (1.0 - self.0) }}

fn calculate_price(price: f64, discount: &dyn Discount) -> f64 { discount.apply(price)}

Agora, para adicionar um novo desconto (por exemplo, BlackFridayDiscount), não precisamos alterar calculate_price — basta implementar o trait Discount.



3. Liskov Substitution Principle (LSP): Princípio da Substituição de Liskov

Essência: Objetos de subtipos (em Rust, tipos que implementam um trait) devem ser intercambiáveis com objetos do tipo base sem quebrar a correção do programa.

Em Rust, isso significa que se você tem um trait e várias implementações dele, o código que trabalha com dyn Trait ou genéricos não deve quebrar ao substituir qualquer uma delas.



Exemplo de violação do LSP

trait Bird {    fn fly(&self) -> String;}

struct Sparrow;impl Bird for Sparrow { fn fly(&self) -> String { "Sparrow flies".to_string() }}

struct Penguin;impl Bird for Penguin { fn fly(&self) -> String { panic!("Penguins can't fly!") }}

Se substituirmos Sparrow por Penguin, o código quebra. A solução é não incluir métodos que não podem ser implementados por todos os subtipos. É melhor separar os traits:



trait CanFly {    fn fly(&self) -> Stri                

Blogs

Book Recommendations