Padrões de Projeto em Rust com Exemplos
Padrões de projeto são soluções comprovadas para problemas típicos no desenvolvimento de software. Em Rust, devido ao seu sistema único de ownership, borrowing e lifetimes, a implementação dos padrões clássicos do GoF frequentemente requer uma abordagem especial. Neste artigo, vamos analisar vários padrões populares com exemplos de código real em Rust.
Por que o Rust muda a abordagem dos padrões?
Diferente de linguagens com garbage collection (Java, C#), o Rust impõe regras rigorosas sobre ownership de memória. Isso torna algumas implementações clássicas (como Singleton através de estado mutável global) inseguras ou ineficientes. No entanto, o Rust oferece suas próprias alternativas elegantes, baseadas em tipos e traits.
1. Builder — criação passo a passo de objetos
Builder é um dos padrões mais usados em Rust. Ele é ideal para configurar estruturas complexas, especialmente quando há muitos campos opcionais.
Exemplo: criando uma requisição HTTP
struct Request { method: String, url: String, headers: Vec<(String, String)>, body: Option<String>,}
struct RequestBuilder { method: String, url: String, headers: Vec<(String, String)>, body: Option<String>,}
impl RequestBuilder { fn new(method: &str, url: &str) -> Self { Self { method: method.to_string(), url: url.to_string(), headers: Vec::new(), body: None, } }
fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self }
fn body(mut self, body: &str) -> Self { self.body = Some(body.to_string()); self }
fn build(self) -> Request { Request { method: self.method, url: self.url, headers: self.headers, body: self.body, } }}
fn main() { let req = RequestBuilder::new("POST", "https://api.example.com/data") .header("Content-Type", "application/json") .header("Authorization", "Bearer token123") .body(r#"{"key": "value"}"#) .build();
println!("URL: {}", req.url); println!("Headers count: {}", req.headers.len());}Vantagens da abordagem em Rust
- Segurança: o objeto só é criado após chamar
build()— configuração incompleta é impossível. - Legibilidade: a cadeia de métodos é intuitiva.
- Type safety: o compilador verifica os tipos em cada etapa.
2. Strategy — seleção de algoritmo em tempo real
Strategy permite alterar o comportamento de um objeto durante a execução. Em Rust, isso é implementado através de traits e dispatch dinâmico (Box com traits) ou estático (generics).
Exemplo: ordenação de dados
trait SortStrategy { fn sort(&self, data: &mut [i32]);}
struct BubbleSort;struct QuickSort;
impl SortStrategy for BubbleSort { fn sort(&self, data: &mut [i32]) { // Implementação simples de bubble sort for i in 0..data.len() { for j in 0..data.len() - 1 - i { if data[j] > data[j + 1] { data.swap(j, j + 1); } } } }}
impl SortStrategy for QuickSort { fn sort(&self, data: &mut [i32]) { // Quick sort (simplificado) if data.len() > 1 { let pivot = data.len() / 2; data.sort(); // Usando o sort embutido como exemplo } }}
struct Sorter { strategy: Box<dyn SortStrategy>,}
impl Sorter { fn new(strategy: Box<dyn SortStrategy>) ->