Padrões de Projeto em Rust com Exemplos: Nível Avançado
Rust é uma linguagem que muda a abordagem de design. Os padrões clássicos do GoF frequentemente exigem adaptação devido ao sistema estrito de ownership, lifetime e à ausência de herança. Neste artigo, vamos analisar padrões de projeto avançados que não apenas funcionam em Rust, mas utilizam seus pontos fortes: segurança de memória, abstrações de custo zero e um poderoso sistema de tipos.
1. Padrão Builder com Verificações em Tempo de Compilação
O Builder clássico em Rust geralmente é implementado através de uma struct com campos opcionais. Em um nível avançado, podemos usar o sistema de tipos para verificar campos obrigatórios em tempo de compilação. Isso elimina erros de runtime do tipo "esqueci de definir um campo obrigatório".
A implementação via Type State (estado do tipo) permite tornar o Builder seguro e autodocumentado.
// Estados para o Builderstruct Empty;struct WithName(String);struct WithAge(u8);struct Ready(WithName, WithAge);
struct PersonBuilder<State> { state: State,}
impl PersonBuilder<Empty> { pub fn new() -> Self { PersonBuilder { state: Empty } } pub fn name(self, name: &str) -> PersonBuilder<WithName> { PersonBuilder { state: WithName(name.to_string()), } }}
impl PersonBuilder<WithName> { pub fn age(self, age: u8) -> PersonBuilder<Ready> { PersonBuilder { state: Ready(self.state, WithAge(age)), } }}
impl PersonBuilder<Ready> { pub fn build(self) -> Person { Person { name: self.state.0 .0, age: self.state.1 .0, } }}
struct Person { name: String, age: u8,}
fn main() { // Compila - todos os campos foram definidos let person = PersonBuilder::new() .name("Alice") .age(30) .build(); // Não compila: falta name() // let person = PersonBuilder::new().age(30).build(); println!("{} tem {} anos", person.name, person.age);}Vantagem: erros de configuração são detectados em tempo de compilação, e não durante a execução. Isso é especialmente valioso para objetos complexos com dezenas de parâmetros.
2. Padrão RAII e Smart Pointers para Gerenciamento de Recursos
RAII (Resource Acquisition Is Initialization) não é apenas um padrão, mas uma filosofia do Rust. Cada recurso (memória, arquivo, socket, lock) deve ser adquirido no construtor e liberado no destrutor. Rust implementa isso através das traits Drop e do sistema de ownership.
Exemplo avançado: criação de um wrapper RAII para um mutex com liberação automática.
use std::ops::Deref;use std::sync::{Arc, Mutex};
struct Guard<T> { data: T,}
impl<T> Drop for Guard<T> { fn drop(&mut self) { println!("Recurso liberado!"); }}
impl<T> Deref for Guard<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.data }}
struct ResourceManager<T> { resource: Arc<Mutex<T>>,}
impl<T> ResourceManager<T> { fn new(data: T) -> Self { ResourceManager { resource: Arc::new(Mutex::new(data)), } } fn acquire(&self) -> Guard<std::sync::MutexGuard<T>> { let guard = self.resource.lock().unwrap(); Guard { data: guard } }}
fn main() { let manager = ResourceManager::new(42); { let guard = manager.acquire(); println!("Valor: {}", *guard); // guard sai de escopo -> libera o mutex automaticamente } // Imprime "Recurso liberado!" println!("Mutex está disponível novamente");}Ideia chave: o destructor é cha ```