---
title: "Padrões de Projeto em Rust: Guia Completo | Rust Brasil"
url: "https://rustlang.com.br/artigos/padroes-projeto-rust/"
markdown_url: "https://rustlang.com.br/artigos/padroes-projeto-rust.MD"
description: "Padrões de projeto em Rust: Builder, Strategy, Observer, State e mais. Implementações idiomáticas com traits e enums."
date: "2026-02-23"
author: "Equipe Rust Brasil"
---

# Padrões de Projeto em Rust: Guia Completo | Rust Brasil

Padrões de projeto em Rust: Builder, Strategy, Observer, State e mais. Implementações idiomáticas com traits e enums.


## Introdução

Padrões de projeto clássicos do Gang of Four (GoF) foram concebidos para linguagens orientadas a objetos com herança. Rust não tem herança, classes ou interfaces tradicionais — em vez disso, oferece traits, enums, ownership e um sistema de tipos expressivo que permite implementar padrões de formas únicas e muitas vezes mais seguras.

Neste artigo, vamos explorar os padrões de projeto mais úteis e idiomáticos em Rust, mostrando como os recursos da linguagem transformam padrões clássicos em soluções elegantes com garantias em tempo de compilação.

## O Problema: Código sem Estrutura

### Não Faça Isso: Construtores com Muitos Parâmetros

```rust
// ERRADO: Construtor com muitos parâmetros — fácil errar a ordem
struct Servidor {
    host: String,
    porta: u16,
    max_conexoes: u32,
    timeout_ms: u64,
    tls: bool,
    certificado: Option<String>,
    log_level: String,
}

impl Servidor {
    fn new(
        host: String,
        porta: u16,
        max_conexoes: u32,
        timeout_ms: u64,
        tls: bool,
        certificado: Option<String>,
        log_level: String,
    ) -> Self {
        Servidor { host, porta, max_conexoes, timeout_ms, tls, certificado, log_level }
    }
}

fn main() {
    // Qual argumento é qual? Fácil trocar 100 com 5000
    let server = Servidor::new(
        "localhost".into(), 8080, 100, 5000, true, None, "info".into()
    );
}
```

### Não Faça Isso: Tipos Primitivos para Tudo

```rust
// ERRADO: Tipos primitivos não previnem erros lógicos
fn transferir(de: u64, para: u64, valor: f64) {
    // de e para são ambos u64 — fácil trocar os argumentos
    println!("Transferindo {valor} da conta {de} para conta {para}");
}

fn main() {
    // Bug silencioso: argumentos trocados, compila sem problemas
    transferir(999, 123, 500.0); // Queria 123 → 999 mas escreveu ao contrário
}
```

## Padrão 1: Builder Pattern

O Builder é o padrão mais comum em Rust para construir structs complexas com validação:

```rust
/// Configuração de um servidor HTTP.
pub struct ServidorConfig {
    host: String,
    porta: u16,
    max_conexoes: u32,
    timeout_ms: u64,
    tls: bool,
}

/// Builder para construir ServidorConfig passo a passo.
pub struct ServidorConfigBuilder {
    host: String,
    porta: u16,
    max_conexoes: u32,
    timeout_ms: u64,
    tls: bool,
}

impl ServidorConfigBuilder {
    pub fn new() -> Self {
        ServidorConfigBuilder {
            host: "127.0.0.1".to_string(),
            porta: 8080,
            max_conexoes: 100,
            timeout_ms: 30_000,
            tls: false,
        }
    }

    pub fn host(mut self, host: &str) -> Self {
        self.host = host.to_string();
        self
    }

    pub fn porta(mut self, porta: u16) -> Self {
        self.porta = porta;
        self
    }

    pub fn max_conexoes(mut self, max: u32) -> Self {
        self.max_conexoes = max;
        self
    }

    pub fn timeout_ms(mut self, timeout: u64) -> Self {
        self.timeout_ms = timeout;
        self
    }

    pub fn tls(mut self, tls: bool) -> Self {
        self.tls = tls;
        self
    }

    pub fn build(self) -> Result<ServidorConfig, String> {
        if self.porta == 0 {
            return Err("Porta não pode ser zero".into());
        }
        if self.max_conexoes == 0 {
            return Err("max_conexoes deve ser maior que zero".into());
        }

        Ok(ServidorConfig {
            host: self.host,
            porta: self.porta,
            max_conexoes: self.max_conexoes,
            timeout_ms: self.timeout_ms,
            tls: self.tls,
        })
    }
}

fn main() {
    // Legível, com valores padrão, e validação no build()
    let config = ServidorConfigBuilder::new()
        .host("0.0.0.0")
        .porta(3000)
        .max_conexoes(500)
        .tls(true)
        .build()
        .expect("Configuração inválida");

    println!("Servidor em {}:{}", config.host, config.porta);
}
```

Com a crate `derive_builder`, você pode gerar o builder automaticamente:

```toml
# Cargo.toml
[dependencies]
derive_builder = "0.20"
```

```rust
use derive_builder::Builder;

#[derive(Builder, Debug)]
#[builder(setter(into))]
pub struct Email {
    destinatario: String,
    assunto: String,
    corpo: String,
    #[builder(default = "false")]
    html: bool,
}

fn main() {
    let email = EmailBuilder::default()
        .destinatario("user@example.com")
        .assunto("Bem-vindo!")
        .corpo("Olá, seja bem-vindo ao sistema.")
        .build()
        .unwrap();

    println!("{:?}", email);
}
```

## Padrão 2: Newtype Pattern

Newtype wraps um tipo primitivo em uma struct para criar um tipo distinto com semântica:

```rust
/// ID de conta bancária — tipo distinto de u64.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ContaId(u64);

impl ContaId {
    pub fn new(id: u64) -> Self {
        ContaId(id)
    }

    pub fn valor(&self) -> u64 {
        self.0
    }
}

/// Valor monetário em centavos — evita erros de ponto flutuante.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Dinheiro(i64);

impl Dinheiro {
    pub fn reais(valor: i64) -> Self {
        Dinheiro(valor * 100)
    }

    pub fn centavos(valor: i64) -> Self {
        Dinheiro(valor)
    }

    pub fn em_centavos(&self) -> i64 {
        self.0
    }

    pub fn em_reais(&self) -> f64 {
        self.0 as f64 / 100.0
    }
}

impl std::fmt::Display for Dinheiro {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "R$ {:.2}", self.em_reais())
    }
}

impl std::ops::Add for Dinheiro {
    type Output = Self;
    fn add(self, rhs: Self) -> Self {
        Dinheiro(self.0 + rhs.0)
    }
}

// Agora é IMPOSSÍVEL trocar conta_origem com conta_destino
fn transferir(de: ContaId, para: ContaId, valor: Dinheiro) -> Result<(), String> {
    if valor.em_centavos() <= 0 {
        return Err("Valor deve ser positivo".into());
    }
    println!("Transferindo {valor} da conta {:?} para {:?}", de, para);
    Ok(())
}

fn main() {
    let alice = ContaId::new(123);
    let bob = ContaId::new(456);
    let valor = Dinheiro::reais(500);

    // transferir(bob, alice, valor) — a ordem é clara pelo nome dos tipos
    transferir(alice, bob, valor).unwrap();

    // Isso NÃO COMPILA — tipos diferentes
    // transferir(123u64, 456u64, 500.0f64);
}
```

## Padrão 3: Typestate Pattern

O Typestate Pattern usa o sistema de tipos para garantir que operações ocorram na ordem correta **em tempo de compilação**:

```rust
use std::marker::PhantomData;

// Estados (tipos sem dados, usados apenas no sistema de tipos)
pub struct Rascunho;
pub struct Revisado;
pub struct Publicado;

/// Documento cujo estado é rastreado pelo sistema de tipos.
pub struct Documento<Estado> {
    titulo: String,
    conteudo: String,
    _estado: PhantomData<Estado>,
}

impl Documento<Rascunho> {
    pub fn novo(titulo: &str) -> Self {
        Documento {
            titulo: titulo.to_string(),
            conteudo: String::new(),
            _estado: PhantomData,
        }
    }

    pub fn escrever(&mut self, texto: &str) {
        self.conteudo.push_str(texto);
    }

    /// Envia para revisão — consome o Rascunho e retorna Revisado
    pub fn enviar_para_revisao(self) -> Documento<Revisado> {
        println!("'{}' enviado para revisão", self.titulo);
        Documento {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: PhantomData,
        }
    }
}

impl Documento<Revisado> {
    /// Aprova e publica — consome Revisado e retorna Publicado
    pub fn aprovar(self) -> Documento<Publicado> {
        println!("'{}' aprovado e publicado", self.titulo);
        Documento {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: PhantomData,
        }
    }

    /// Rejeita e volta para rascunho
    pub fn rejeitar(self, motivo: &str) -> Documento<Rascunho> {
        println!("'{}' rejeitado: {motivo}", self.titulo);
        Documento {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: PhantomData,
        }
    }
}

impl Documento<Publicado> {
    pub fn url(&self) -> String {
        format!("/artigos/{}", self.titulo.to_lowercase().replace(' ', "-"))
    }
}

fn main() {
    // Fluxo correto: Rascunho → Revisado → Publicado
    let mut doc = Documento::<Rascunho>::novo("Meu Artigo");
    doc.escrever("Conteúdo do artigo...");

    let doc = doc.enviar_para_revisao();
    let doc = doc.aprovar();
    println!("Publicado em: {}", doc.url());

    // Isso NÃO COMPILA — não pode publicar diretamente do rascunho:
    // let doc = Documento::<Rascunho>::novo("Teste");
    // doc.aprovar(); // ERRO: método `aprovar` não existe para Documento<Rascunho>
}
```

## Padrão 4: RAII (Resource Acquisition Is Initialization)

Rust implementa RAII nativamente com `Drop`. Recursos são liberados automaticamente quando saem do escopo:

```rust
use std::io::{self, Write, BufWriter};
use std::fs::File;

/// Timer que mede a duração de um escopo automaticamente.
pub struct Timer {
    nome: String,
    inicio: std::time::Instant,
}

impl Timer {
    pub fn new(nome: &str) -> Self {
        println!("[TIMER] '{}' iniciado", nome);
        Timer {
            nome: nome.to_string(),
            inicio: std::time::Instant::now(),
        }
    }
}

impl Drop for Timer {
    fn drop(&mut self) {
        let duracao = self.inicio.elapsed();
        println!("[TIMER] '{}' finalizado em {:?}", self.nome, duracao);
    }
}

/// Guard para arquivo temporário que é deletado ao sair do escopo.
pub struct ArquivoTemporario {
    caminho: std::path::PathBuf,
}

impl ArquivoTemporario {
    pub fn new(nome: &str) -> io::Result<Self> {
        let caminho = std::env::temp_dir().join(nome);
        File::create(&caminho)?;
        Ok(ArquivoTemporario { caminho })
    }

    pub fn escrever(&self, conteudo: &str) -> io::Result<()> {
        let file = File::create(&self.caminho)?;
        let mut writer = BufWriter::new(file);
        writer.write_all(conteudo.as_bytes())?;
        writer.flush()
    }

    pub fn caminho(&self) -> &std::path::Path {
        &self.caminho
    }
}

impl Drop for ArquivoTemporario {
    fn drop(&mut self) {
        if let Err(e) = std::fs::remove_file(&self.caminho) {
            eprintln!("Aviso: não foi possível remover {:?}: {e}", self.caminho);
        } else {
            println!("Arquivo temporário {:?} removido", self.caminho);
        }
    }
}

fn main() -> io::Result<()> {
    let _timer = Timer::new("main");

    {
        let temp = ArquivoTemporario::new("dados.tmp")?;
        temp.escrever("dados temporários")?;
        println!("Arquivo em: {:?}", temp.caminho());
        // temp é automaticamente deletado aqui
    }

    println!("Arquivo temporário já foi removido");
    Ok(())
}
```

## Padrão 5: Strategy Pattern com Traits

Traits em Rust substituem interfaces e permitem polimorfismo em tempo de compilação (generics) ou em tempo de execução (trait objects):

```rust
/// Trait que define a estratégia de cálculo de desconto.
pub trait Desconto {
    fn calcular(&self, valor: f64) -> f64;
    fn nome(&self) -> &str;
}

pub struct SemDesconto;
impl Desconto for SemDesconto {
    fn calcular(&self, valor: f64) -> f64 { valor }
    fn nome(&self) -> &str { "Sem desconto" }
}

pub struct DescontoPercentual {
    percentual: f64,
}

impl DescontoPercentual {
    pub fn new(percentual: f64) -> Self {
        DescontoPercentual { percentual }
    }
}

impl Desconto for DescontoPercentual {
    fn calcular(&self, valor: f64) -> f64 {
        valor * (1.0 - self.percentual / 100.0)
    }
    fn nome(&self) -> &str { "Desconto percentual" }
}

pub struct DescontoFixo {
    valor_desconto: f64,
}

impl DescontoFixo {
    pub fn new(valor: f64) -> Self {
        DescontoFixo { valor_desconto: valor }
    }
}

impl Desconto for DescontoFixo {
    fn calcular(&self, valor: f64) -> f64 {
        (valor - self.valor_desconto).max(0.0)
    }
    fn nome(&self) -> &str { "Desconto fixo" }
}

/// Carrinho que aceita qualquer estratégia de desconto.
pub struct Carrinho {
    itens: Vec<(String, f64)>,
    desconto: Box<dyn Desconto>,
}

impl Carrinho {
    pub fn new(desconto: Box<dyn Desconto>) -> Self {
        Carrinho {
            itens: Vec::new(),
            desconto,
        }
    }

    pub fn adicionar(&mut self, nome: &str, preco: f64) {
        self.itens.push((nome.to_string(), preco));
    }

    pub fn total(&self) -> f64 {
        let subtotal: f64 = self.itens.iter().map(|(_, p)| p).sum();
        self.desconto.calcular(subtotal)
    }
}

fn main() {
    let mut carrinho = Carrinho::new(Box::new(DescontoPercentual::new(15.0)));
    carrinho.adicionar("Teclado", 250.0);
    carrinho.adicionar("Mouse", 150.0);
    println!("Total com 15% de desconto: R$ {:.2}", carrinho.total());

    let mut carrinho2 = Carrinho::new(Box::new(DescontoFixo::new(50.0)));
    carrinho2.adicionar("Monitor", 1200.0);
    println!("Total com R$50 de desconto: R$ {:.2}", carrinho2.total());
}
```

## Padrão 6: Observer com Channels

Em vez de callbacks, Rust usa channels para comunicação desacoplada:

```rust
use std::sync::mpsc;
use std::thread;

#[derive(Debug, Clone)]
pub enum Evento {
    UsuarioCriado { id: u64, nome: String },
    PedidoRealizado { id: u64, valor: f64 },
    PagamentoConfirmado { pedido_id: u64 },
}

/// Publisher que envia eventos para múltiplos subscribers.
pub struct EventBus {
    senders: Vec<mpsc::Sender<Evento>>,
}

impl EventBus {
    pub fn new() -> Self {
        EventBus { senders: Vec::new() }
    }

    pub fn subscribe(&mut self) -> mpsc::Receiver<Evento> {
        let (tx, rx) = mpsc::channel();
        self.senders.push(tx);
        rx
    }

    pub fn publicar(&self, evento: Evento) {
        // Remove senders desconectados mantendo os ativos
        for sender in &self.senders {
            let _ = sender.send(evento.clone());
        }
    }
}

fn main() {
    let mut bus = EventBus::new();

    // Subscriber 1: Logger
    let rx_logger = bus.subscribe();
    let logger = thread::spawn(move || {
        while let Ok(evento) = rx_logger.recv() {
            println!("[LOG] Evento recebido: {evento:?}");
        }
    });

    // Subscriber 2: Notificador
    let rx_notif = bus.subscribe();
    let notificador = thread::spawn(move || {
        while let Ok(evento) = rx_notif.recv() {
            if let Evento::PedidoRealizado { id, valor } = evento {
                println!("[NOTIF] Novo pedido #{id}: R$ {valor:.2}");
            }
        }
    });

    // Publicar eventos
    bus.publicar(Evento::UsuarioCriado {
        id: 1,
        nome: "Maria".into(),
    });
    bus.publicar(Evento::PedidoRealizado { id: 100, valor: 299.90 });
    bus.publicar(Evento::PagamentoConfirmado { pedido_id: 100 });

    // Fechar o bus (drop dos senders)
    drop(bus);

    logger.join().unwrap();
    notificador.join().unwrap();
}
```

## Padrão 7: Command Pattern com Enums

Enums em Rust são ideais para representar comandos:

```rust
/// Comandos para um editor de texto.
#[derive(Debug)]
pub enum Comando {
    Inserir { posicao: usize, texto: String },
    Deletar { posicao: usize, quantidade: usize },
    Substituir { de: String, para: String },
}

pub struct Editor {
    conteudo: String,
    historico: Vec<(Comando, String)>, // (comando, estado anterior)
}

impl Editor {
    pub fn new(conteudo: &str) -> Self {
        Editor {
            conteudo: conteudo.to_string(),
            historico: Vec::new(),
        }
    }

    pub fn executar(&mut self, comando: Comando) {
        let estado_anterior = self.conteudo.clone();

        match &comando {
            Comando::Inserir { posicao, texto } => {
                self.conteudo.insert_str(*posicao, texto);
            }
            Comando::Deletar { posicao, quantidade } => {
                let fim = (*posicao + *quantidade).min(self.conteudo.len());
                self.conteudo.drain(*posicao..fim);
            }
            Comando::Substituir { de, para } => {
                self.conteudo = self.conteudo.replace(de, para);
            }
        }

        self.historico.push((comando, estado_anterior));
    }

    pub fn desfazer(&mut self) -> bool {
        if let Some((_comando, estado_anterior)) = self.historico.pop() {
            self.conteudo = estado_anterior;
            true
        } else {
            false
        }
    }

    pub fn conteudo(&self) -> &str {
        &self.conteudo
    }
}

fn main() {
    let mut editor = Editor::new("Olá Mundo");
    println!("Inicial: '{}'", editor.conteudo());

    editor.executar(Comando::Substituir {
        de: "Mundo".into(),
        para: "Rust".into(),
    });
    println!("Após substituir: '{}'", editor.conteudo());

    editor.executar(Comando::Inserir {
        posicao: 4,
        texto: ", bem-vindo ao".into(),
    });
    println!("Após inserir: '{}'", editor.conteudo());

    editor.desfazer();
    println!("Após desfazer: '{}'", editor.conteudo());

    editor.desfazer();
    println!("Após desfazer: '{}'", editor.conteudo());
}
```

## Armadilhas Comuns

### 1. Builder sem Validação

```rust
// ERRADO: Builder que sempre retorna Ok
impl ServidorConfigBuilder {
    pub fn build(self) -> ServidorConfig {
        // Nenhuma validação — aceita configuração inválida
        ServidorConfig { /* ... */ }
    }
}

// CORRETO: build() retorna Result
impl ServidorConfigBuilder {
    pub fn build(self) -> Result<ServidorConfig, String> {
        // Valida antes de construir
        if self.porta == 0 {
            return Err("Porta inválida".into());
        }
        Ok(ServidorConfig { /* ... */ })
    }
}
```

### 2. Newtype sem Implementar Traits Necessários

```rust
// ERRADO: Newtype sem Debug, Clone, etc.
struct UserId(u64);
// Não pode imprimir, copiar, comparar...

// CORRETO: Derive os traits necessários
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct UserId(u64);
```

### 3. Trait Objects Onde Generics Bastam

```rust
// Desnecessário para um único tipo: Box<dyn> tem overhead de indireção
fn processar(estrategia: Box<dyn Desconto>, valor: f64) -> f64 {
    estrategia.calcular(valor)
}

// Melhor quando o tipo é conhecido em compilação: sem overhead
fn processar<D: Desconto>(estrategia: &D, valor: f64) -> f64 {
    estrategia.calcular(valor)
}
```

## Quando Usar Cada Padrão

| Padrão | Use Quando |
|---|---|
| **Builder** | Struct com muitos campos ou configuração complexa |
| **Newtype** | Distinguir tipos primitivos com mesma representação |
| **Typestate** | Garantir sequência de operações em tempo de compilação |
| **RAII** | Gerenciar recursos (arquivos, locks, conexões) |
| **Strategy** | Algoritmos intercambiáveis, polimorfismo |
| **Observer** | Comunicação desacoplada entre componentes |
| **Command** | Operações reversíveis, filas de comandos |

---

## Veja Também

- [Tutorial: Traits e Generics](/tutoriais/traits-generics/) — Fundamentos de traits para padrões de projeto
- [Tutorial: Structs, Enums e Pattern Matching](/tutoriais/structs-enums-pattern-matching/) — Base para Newtype e Command
- [Boas Práticas de Error Handling](/artigos/boas-praticas-error-handling/) — Padrões para tipos de erro
- [Documentação em Rust](/artigos/documentacao-rust/) — Documente seus padrões com doc tests
- [Migração de Python para Rust](/artigos/migracao-python-para-rust/) — Padrões OOP vs Rust idiomático

---

Padrões de projeto se adaptam a cada linguagem. Veja como são aplicados em outros ecossistemas:

- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Design patterns em Go: interfaces implícitas, composição e padrões idiomáticos</a>
- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Padrões de projeto em Kotlin: sealed classes, delegation e DSLs</a>
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Design patterns em Python: decorators, metaclasses e padrões pythônicos</a>
