Princípios SOLID em Rust: teoria e prática (nível avançado)

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: SOLID no mundo Rust



Os princípios SOLID são a base de uma arquitetura flexível e sustentável. No entanto, sua implementação em Rust possui especificidades próprias, devido ao sistema único de ownership, traits e à ausência de POO clássica. Neste artigo, vamos analisar como aplicar cada um dos cinco princípios em nível avançado, usando Rust idiomático. Não vamos apenas listar regras, mas mostrar como elas se transformam no contexto do sistema de tipos e borrowing.



Este artigo é destinado a desenvolvedores que já conhecem os fundamentos do Rust e do SOLID. Vamos nos concentrar nos detalhes: como evitar violações do LSP ao trabalhar com tipos genéricos, como o ISP afeta o design de traits e por que o DIP em Rust geralmente exige o uso de trait objects ou Box.



1. Princípio da Responsabilidade Única (SRP) e Ownership



Em Rust, o SRP ganha uma dimensão extra devido às regras estritas de ownership. Uma classe (ou struct) deve ter apenas um motivo para mudar. Mas como isso se relaciona com a transferência de ownership?



Problema: struct gerenciando recursos e lógica

Considere um exemplo onde uma struct gerencia simultaneamente um descritor de arquivo e faz parsing de dados. Isso viola o SRP.



use std::fs::File;use std::io::Read;

// ❌ Violação do SRP: a struct gerencia o recurso e processa os dadosstruct DataProcessor { file: File,}

impl DataProcessor { fn new(path: &str) -> Self { DataProcessor { file: File::open(path).unwrap(), } }

fn process(&mut self) -> Vec<u8> { let mut buffer = Vec::new(); self.file.read_to_end(&mut buffer).unwrap(); // ... lógica complexa de processamento buffer }}


Solução: separação através da transferência de ownership

Separamos as responsabilidades: uma struct é responsável pela leitura, outra pelo processamento. O ownership é transferido explicitamente.



use std::fs::File;use std::io::Read;

// ✅ Primeira responsabilidade: gerenciamento do arquivostruct FileReader { file: File,}

impl FileReader { fn new(path: &str) -> Self { FileReader { file: File::open(path).unwrap(), } }

fn read(self) -> Vec<u8> { let mut buffer = Vec::new(); // self.file é consumido (moved), pois não é mais necessário após a leitura self.file.read_to_end(&mut buffer).unwrap(); buffer }}

// ✅ Segunda responsabilidade: processamento dos dadosstruct DataProcessor { data: Vec<u8>,}

impl DataProcessor { fn new(data: Vec<u8>) -> Self { DataProcessor { data } }

fn process(&self) -> Vec<u8> { // Processamento imutável self.data.iter().map(|&b| b + 1).collect() }}

fn main() { let reader = FileReader::new("data.bin"); let raw_data = reader.read(); // reader não está mais disponível let processor = DataProcessor::new(raw_data); let result = processor.process();}


Ponto-chave: usamos self (ownership) em vez de &self em read() para indicar explicitamente que, após a leitura, FileReader não é mais necessário. Isso torna o fluxo de dados transparente e previne vazamentos de recursos.



2. Princípio do Aberto/Fechado (OCP) e Traits



OCP afirma: entidades devem estar abertas para extensão, mas fechadas para modificação. Em Rust, isso é perfeitamente implementado através de traits e genéricos, sem usar tabelas virtuais (vtable), a menos que seja necessário.



Exemplo avançado: estratégia com custo zero de overhead

Suponha que temos um sistema de logging. Em vez de modificar o núcleo da aplicação para cada novo backend, usamos polimorfismo através de traits.



/                

Blogs

Book Recommendations