Padrões de Projeto em Rust: não apenas moda, mas uma necessidade
Quando falamos de Rust, muitos imaginam um sistema de tipos, borrow checker e ausência de coletor de lixo. Mas por trás dessa fachada de rigor, esconde-se uma ferramenta poderosa para projetar arquitetura. Os padrões de projeto (design patterns) em Rust não são apenas uma homenagem às tradições do Java ou C++. São uma forma de fazer o compilador trabalhar a seu favor, e não contra você.
Neste artigo, vamos explorar por que os padrões clássicos do GoF (Gang of Four) em Rust são diferentes, quais deles são realmente úteis e quais são antipadrões. E o mais importante: veremos código real que pode ser aplicado imediatamente no seu projeto.
Por que o Rust muda as regras do jogo?
Os padrões de projeto clássicos geralmente resolvem problemas relacionados ao gerenciamento de memória, herança e polimorfismo. Rust oferece uma abordagem completamente diferente:
- Propriedade e empréstimo — em vez de coleta de lixo, você gerencia a memória explicitamente. Padrões que em outras linguagens resolvem o problema de "quem é o dono do objeto" tornam-se triviais ou desnecessários em Rust.
- Traits — substituem a herança. Rust não suporta herança OOP clássica, mas as traits oferecem flexibilidade sem os problemas da herança em diamante.
- Enums e correspondência de padrões — uma ferramenta poderosa que substitui muitos padrões relacionados a estado e ramificação.
- Estilo funcional — iteradores, closures e combinadores permitem escrever código conciso sem padrões explícitos.
Portanto, muitos padrões em Rust não se parecem com "classe e interface", mas sim com trait + struct + enum. Vamos ver exemplos concretos.
Padrão Builder: construindo objetos complexos
O padrão Builder é um dos mais populares em Rust. Ele é ideal para criar objetos com muitos parâmetros, especialmente quando alguns deles são opcionais.
Problema: construtor com 10 parâmetros
Imagine que você precisa criar uma configuração de cliente HTTP. Na abordagem tradicional, você escreveria:
let client = HttpClient::new( "https://api.example.com", Duration::from_secs(30), 5, true, None, vec!["Accept: application/json".to_string()], ...);Isso é ilegível e fácil de errar a ordem dos argumentos. O Builder resolve esse problema.
Implementação em Rust
Em Rust, o padrão Builder é implementado através de uma struct separada com métodos setters que retornam self:
#[derive(Debug)]pub struct HttpClient { base_url: String, timeout: Duration, max_retries: u32, verify_ssl: bool, headers: Vec<String>,}
pub struct HttpClientBuilder { base_url: String, timeout: Duration, max_retries: u32, verify_ssl: bool, headers: Vec<String>,}
impl HttpClientBuilder { pub fn new(base_url: &str) -> Self { HttpClientBuilder { base_url: base_url.to_string(), timeout: Duration::from_secs(30), max_retries: 3, verify_ssl: true, headers: Vec::new(), } }
pub fn timeout(mut self, duration: Duration) -> Self { self.timeout = duration; self }
pub fn max_retries(mut self, n: u32) -> Self { self.max_retries = n; self }
pub fn no_ssl(mut self) -> Self { self.verify_ssl = false; self }
pub fn add_header(mut self, header: &str) -> Self { self.headers.push(header.to_string()); self }
pub fn build(self) -> Result<HttpClient, String> { if self.base_url.is_empty() { return Err("base_url cannot be empty".to_string()); } Ok(HttpClient { base_url: self.base_url,