Padrões de Projeto em Rust: Guia Prático com Exemplos
Padrões de projeto são soluções comprovadas para problemas típicos de desenvolvimento. Em Rust, devido ao seu sistema único de ownership e modelo de memória, a implementação de padrões clássicos muitas vezes difere de linguagens com garbage collection. Neste artigo, vamos abordar três padrões principais: Singleton, Builder e Strategy, adaptados às particularidades do Rust.
1. Padrão Singleton em Rust: Estado global sem unsafe
O Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Em Rust, a crate once_cell ou o estável std::sync::OnceLock (a partir do Rust 1.70) são ideais para isso.
Implementação com OnceLock
use std::sync::OnceLock;
pub struct Logger { level: String,}
impl Logger { pub fn global() -> &'static Logger { static INSTANCE: OnceLock<Logger> = OnceLock::new(); INSTANCE.get_or_init(|| Logger { level: "info".to_string(), }) }
pub fn log(&self, msg: &str) { println!("[{}] {}", self.level, msg); }}
fn main() { let logger = Logger::global(); logger.log("Olá, Rust!"); // Logger::global() retornará a mesma instância}Vantagens: Segurança (sem unsafe), inicialização lazy, thread safety. Desvantagem — estado global dificulta os testes.
2. Padrão Builder: Construção de objetos complexos
O Builder separa a construção de um objeto complexo de sua representação. Em Rust, ele é especialmente útil para structs com muitos campos opcionais.
Exemplo: Configuração de um cliente HTTP
pub struct HttpClient { url: String, timeout: u64, headers: Vec<(String, String)>,}
pub struct HttpClientBuilder { url: String, timeout: Option<u64>, headers: Vec<(String, String)>,}
impl HttpClientBuilder { pub fn new(url: &str) -> Self { HttpClientBuilder { url: url.to_string(), timeout: None, headers: vec![], } }
pub fn timeout(mut self, secs: u64) -> Self { self.timeout = Some(secs); self }
pub fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self }
pub fn build(self) -> HttpClient { HttpClient { url: self.url, timeout: self.timeout.unwrap_or(30), headers: self.headers, } }}
fn main() { let client = HttpClientBuilder::new("https://api.example.com") .timeout(10) .header("Authorization", "Bearer token123") .build(); println!("URL do cliente: {}", client.url);}Importante: O uso de self (não &self) nos métodos do builder evita clonagem e torna a API mais ergonômica.
3. Padrão Strategy: Algoritmos como estratégias intercambiáveis
O Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Em Rust, isso é implementado através de traits e dispatch dinâmico (Box<dyn Trait>).
Exemplo: Sistema de ordenação
trait SortStrategy { fn sort(&self, data: &mut [i32]);}
struct BubbleSort;impl SortStrategy for BubbleSort { fn sort(&self, data: &mut [i32]) { let n = data.len(); for i in 0..n { for j in 0..n - i - 1 { if data[j] > data[j + 1] { data.swap(j, j + 1); } } } }}
struct QuickSort;impl SortStrategy for QuickSort { fn sort(&self, da
```