Por que a arquitetura em Rust não é um luxo, mas uma necessidade
Quando você escreve em Rust, o sistema de tipos e o borrow checker já lhe dão 80% de proteção contra erros. Mas a segurança de memória é apenas a base. Se você construir sobre ela uma arquitetura caótica, mais cedo ou mais tarde o projeto se transformará em um labirinto de dependências mútuas, onde a alteração de um módulo quebra outros dez.
Os padrões arquiteturais em Rust não são uma questão de moda nem uma relíquia do mundo OOP. São soluções comprovadas para problemas típicos: como criar um objeto que não viole invariantes? Como conectar componentes para que possam ser testados individualmente? Como organizar a lógica para que possa ser estendida sem modificar o código existente?
Neste artigo, vamos analisar três grupos de padrões (criacionais, estruturais, comportamentais) com exemplos reais em Rust. Nenhuma teoria abstrata — apenas código que pode ser aplicado imediatamente.
Padrões Criacionais: Criando Objetos Corretamente
Builder — o salvador de construtores com 10 parâmetros
Em Rust não há sobrecarga de construtores. Se uma estrutura tiver mais de 3–4 campos, escrever um único construtor para todos os casos significa condenar o usuário a ler uma longa assinatura com Option e Default. O Builder resolve esse problema de forma elegante.
struct ServerConfig { host: String, port: u16, workers: u8, enable_tls: bool, max_connections: u32,}
struct ServerConfigBuilder { host: Option<String>, port: Option<u16>, workers: Option<u8>, enable_tls: Option<bool>, max_connections: Option<u32>,}
impl ServerConfigBuilder { fn new() -> Self { Self { host: None, port: None, workers: None, enable_tls: None, max_connections: None, } }
fn host(mut self, host: &str) -> Self { self.host = Some(host.to_string()); self }
fn port(mut self, port: u16) -> Self { self.port = Some(port); self }
fn workers(mut self, workers: u8) -> Self { self.workers = Some(workers); self }
fn enable_tls(mut self, enable: bool) -> Self { self.enable_tls = Some(enable); self }
fn max_connections(mut self, max: u32) -> Self { self.max_connections = Some(max); self }
fn build(self) -> Result<ServerConfig, String> { Ok(ServerConfig { host: self.host.unwrap_or_else(|| "127.0.0.1".to_string()), port: self.port.unwrap_or(8080), workers: self.workers.unwrap_or(4), enable_tls: self.enable_tls.unwrap_or(false), max_connections: self.max_connections.unwrap_or(100), }) }}
// Usolet config = ServerConfigBuilder::new() .host("0.0.0.0") .port(443) .enable_tls(true) .build() .expect("Configuração inválida");Por que isso funciona em Rust? O padrão Builder combina perfeitamente com o sistema de ownership: a cadeia de métodos consome e retorna self, e o build final retorna o objeto pronto ou um erro de validação. Sem referências mutáveis e sem condições de corrida.
Fábrica — quando o tipo de retorno depende do parâmetro
Imagine que você precisa analisar diferentes formatos: JSON, YAML, TOML. Em vez de usar match em todos os lugares, usamos um método de fábrica.
trait Parser { fn parse(&self, input: &str) -> Result<serde_json::Value, String>;}
struct JsonParser;impl Parser for JsonParser { fn parse(&self, input: &str) -> Result<serde_json::Value, String> { serde_json::from_str(input).map_err(|e| e.to_string()) }}
struct YamlParser;impl Parser for YamlParser { fn parse(&self, input: &str) -> Result<serde_json::