Padrões Arquiteturais em Rust: Como Escrever Aplicações Confiáveis sem Dor

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

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::

Blogs

Book Recommendations