State Pattern em Rust: Máquinas de Estado com Enums e Tipos

Implementação completa do padrão State em Rust com enums, type-state pattern, match exaustivo e exemplos práticos de máquina de estados TCP e processamento de pedidos.

O State Pattern (Padrão de Estado) permite que um objeto altere seu comportamento quando seu estado interno muda, como se o objeto trocasse de classe. Em Rust, este padrão tem uma implementação excepcionalmente poderosa graças a enums e pattern matching exaustivo.

Enquanto em linguagens orientadas a objetos o State Pattern tipicamente envolve interfaces e classes concretas para cada estado, Rust oferece uma abordagem superior: enums que representam todos os estados possíveis, com o compilador garantindo que cada estado seja tratado. Nenhum estado é esquecido, nenhuma transição inválida passa despercebida.

Problema

Considere um sistema de processamento de pedidos. Um pedido passa por vários estados: criado, pago, enviado, entregue, cancelado. Sem o State Pattern, o código fica repleto de condicionais:

// ANTI-PADRÃO: estado como string com ifs por toda parte
fn processar_pedido(pedido: &mut Pedido) {
    if pedido.estado == "criado" {
        // lógica de criado...
    } else if pedido.estado == "pago" {
        // lógica de pago...
    } else if pedido.estado == "enviado" {
        // lógica de enviado...
    }
    // E se esquecermos um estado? O compilador não avisa!
}

Problemas: estados representados como strings são frágeis, transições inválidas são possíveis em runtime, e adicionar um novo estado exige revisar todo o código manualmente.

Solução em Rust

Abordagem com Enum (mais idiomática)

use std::time::SystemTime;

/// Todos os estados possíveis de um pedido
#[derive(Debug, Clone)]
enum EstadoPedido {
    Criado {
        data_criacao: SystemTime,
    },
    AguardandoPagamento {
        data_criacao: SystemTime,
        valor_total: f64,
    },
    Pago {
        data_pagamento: SystemTime,
        id_transacao: String,
    },
    EmPreparacao {
        data_pagamento: SystemTime,
        responsavel: String,
    },
    Enviado {
        codigo_rastreio: String,
        transportadora: String,
    },
    Entregue {
        data_entrega: SystemTime,
        recebedor: String,
    },
    Cancelado {
        motivo: String,
        data_cancelamento: SystemTime,
    },
}

/// O pedido com seus itens e estado atual
#[derive(Debug)]
struct Pedido {
    id: u64,
    itens: Vec<ItemPedido>,
    estado: EstadoPedido,
    historico: Vec<String>,
}

#[derive(Debug, Clone)]
struct ItemPedido {
    nome: String,
    quantidade: u32,
    preco_unitario: f64,
}

/// Erros possíveis nas transições de estado
#[derive(Debug)]
enum ErroPedido {
    TransicaoInvalida { de: String, para: String },
    PedidoVazio,
    ValorInvalido(f64),
}

impl std::fmt::Display for ErroPedido {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ErroPedido::TransicaoInvalida { de, para } => {
                write!(f, "Transição inválida: {} -> {}", de, para)
            }
            ErroPedido::PedidoVazio => write!(f, "Pedido sem itens"),
            ErroPedido::ValorInvalido(v) => write!(f, "Valor inválido: {}", v),
        }
    }
}

impl Pedido {
    fn novo(id: u64) -> Self {
        Pedido {
            id,
            itens: Vec::new(),
            estado: EstadoPedido::Criado {
                data_criacao: SystemTime::now(),
            },
            historico: vec!["Pedido criado".to_string()],
        }
    }

    fn adicionar_item(&mut self, nome: &str, qtd: u32, preco: f64) {
        self.itens.push(ItemPedido {
            nome: nome.to_string(),
            quantidade: qtd,
            preco_unitario: preco,
        });
    }

    fn valor_total(&self) -> f64 {
        self.itens.iter()
            .map(|item| item.preco_unitario * item.quantidade as f64)
            .sum()
    }

    /// Solicita pagamento — transição Criado -> AguardandoPagamento
    fn solicitar_pagamento(&mut self) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::Criado { data_criacao } => {
                if self.itens.is_empty() {
                    return Err(ErroPedido::PedidoVazio);
                }
                let valor = self.valor_total();
                self.estado = EstadoPedido::AguardandoPagamento {
                    data_criacao: *data_criacao,
                    valor_total: valor,
                };
                self.historico.push(format!(
                    "Pagamento solicitado: R${:.2}", valor
                ));
                Ok(())
            }
            outro => Err(ErroPedido::TransicaoInvalida {
                de: format!("{:?}", outro),
                para: "AguardandoPagamento".to_string(),
            }),
        }
    }

    /// Confirma pagamento — transição AguardandoPagamento -> Pago
    fn confirmar_pagamento(&mut self, id_transacao: &str) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::AguardandoPagamento { .. } => {
                self.estado = EstadoPedido::Pago {
                    data_pagamento: SystemTime::now(),
                    id_transacao: id_transacao.to_string(),
                };
                self.historico.push(format!(
                    "Pagamento confirmado: {}", id_transacao
                ));
                Ok(())
            }
            outro => Err(ErroPedido::TransicaoInvalida {
                de: format!("{:?}", outro),
                para: "Pago".to_string(),
            }),
        }
    }

    /// Inicia preparação — transição Pago -> EmPreparacao
    fn iniciar_preparacao(&mut self, responsavel: &str) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::Pago { data_pagamento, .. } => {
                self.estado = EstadoPedido::EmPreparacao {
                    data_pagamento: *data_pagamento,
                    responsavel: responsavel.to_string(),
                };
                self.historico.push(format!(
                    "Preparação iniciada por {}", responsavel
                ));
                Ok(())
            }
            outro => Err(ErroPedido::TransicaoInvalida {
                de: format!("{:?}", outro),
                para: "EmPreparacao".to_string(),
            }),
        }
    }

    /// Envia pedido — transição EmPreparacao -> Enviado
    fn enviar(&mut self, codigo: &str, transportadora: &str) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::EmPreparacao { .. } => {
                self.estado = EstadoPedido::Enviado {
                    codigo_rastreio: codigo.to_string(),
                    transportadora: transportadora.to_string(),
                };
                self.historico.push(format!(
                    "Enviado via {} - rastreio: {}", transportadora, codigo
                ));
                Ok(())
            }
            outro => Err(ErroPedido::TransicaoInvalida {
                de: format!("{:?}", outro),
                para: "Enviado".to_string(),
            }),
        }
    }

    /// Confirma entrega — transição Enviado -> Entregue
    fn confirmar_entrega(&mut self, recebedor: &str) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::Enviado { .. } => {
                self.estado = EstadoPedido::Entregue {
                    data_entrega: SystemTime::now(),
                    recebedor: recebedor.to_string(),
                };
                self.historico.push(format!(
                    "Entregue para {}", recebedor
                ));
                Ok(())
            }
            outro => Err(ErroPedido::TransicaoInvalida {
                de: format!("{:?}", outro),
                para: "Entregue".to_string(),
            }),
        }
    }

    /// Cancela pedido — permitido apenas antes do envio
    fn cancelar(&mut self, motivo: &str) -> Result<(), ErroPedido> {
        match &self.estado {
            EstadoPedido::Enviado { .. }
            | EstadoPedido::Entregue { .. }
            | EstadoPedido::Cancelado { .. } => {
                Err(ErroPedido::TransicaoInvalida {
                    de: format!("{:?}", self.estado),
                    para: "Cancelado".to_string(),
                })
            }
            _ => {
                self.estado = EstadoPedido::Cancelado {
                    motivo: motivo.to_string(),
                    data_cancelamento: SystemTime::now(),
                };
                self.historico.push(format!("Cancelado: {}", motivo));
                Ok(())
            }
        }
    }

    /// Descrição legível do estado atual
    fn descricao_estado(&self) -> String {
        match &self.estado {
            EstadoPedido::Criado { .. } => "Pedido criado".to_string(),
            EstadoPedido::AguardandoPagamento { valor_total, .. } => {
                format!("Aguardando pagamento de R${:.2}", valor_total)
            }
            EstadoPedido::Pago { id_transacao, .. } => {
                format!("Pago (transação: {})", id_transacao)
            }
            EstadoPedido::EmPreparacao { responsavel, .. } => {
                format!("Em preparação por {}", responsavel)
            }
            EstadoPedido::Enviado { codigo_rastreio, transportadora } => {
                format!("Enviado via {} ({})", transportadora, codigo_rastreio)
            }
            EstadoPedido::Entregue { recebedor, .. } => {
                format!("Entregue para {}", recebedor)
            }
            EstadoPedido::Cancelado { motivo, .. } => {
                format!("Cancelado: {}", motivo)
            }
        }
    }
}

fn main() {
    let mut pedido = Pedido::novo(1001);
    pedido.adicionar_item("Teclado Mecânico", 1, 450.00);
    pedido.adicionar_item("Mouse Wireless", 1, 150.00);

    println!("Estado: {}", pedido.descricao_estado());

    // Fluxo normal do pedido
    pedido.solicitar_pagamento().unwrap();
    println!("Estado: {}", pedido.descricao_estado());

    pedido.confirmar_pagamento("PIX-2026-001").unwrap();
    println!("Estado: {}", pedido.descricao_estado());

    pedido.iniciar_preparacao("João").unwrap();
    pedido.enviar("BR123456789", "Correios").unwrap();
    pedido.confirmar_entrega("Maria").unwrap();

    println!("\nEstado final: {}", pedido.descricao_estado());

    // Tentar cancelar um pedido entregue — erro!
    match pedido.cancelar("Desisti") {
        Ok(_) => println!("Cancelado"),
        Err(e) => println!("Erro esperado: {}", e),
    }

    println!("\nHistórico:");
    for entrada in &pedido.historico {
        println!("  → {}", entrada);
    }
}

Diagrama

    Máquina de Estados do Pedido:

    ┌─────────┐    solicitar_    ┌──────────────┐   confirmar_   ┌───────┐
    │ Criado  │───pagamento()──▶│ Aguardando   │──pagamento()─▶│ Pago  │
    └────┬────┘                  │ Pagamento    │               └───┬───┘
         │                       └──────┬───────┘                   │
         │ cancelar()                   │ cancelar()                │ iniciar_
         │                              │                           │ preparacao()
         ▼                              ▼                           ▼
    ┌──────────┐               ┌──────────┐               ┌──────────────┐
    │Cancelado │◀──────────────│Cancelado │               │Em Preparação │
    └──────────┘               └──────────┘               └──────┬───────┘
                                                                  │
                                                                  │ cancelar()
                                    ┌──────────┐                  │
                                    │Cancelado │◀─────────────────┤
                                    └──────────┘                  │
                                                                  │ enviar()
                                                                  ▼
                               ┌──────────┐               ┌──────────┐
                               │ Entregue │◀──confirmar_──│ Enviado  │
                               └──────────┘   entrega()   └──────────┘

    Match exaustivo garante que todo estado é tratado:

    match estado {
        Criado { .. }              => ✓ tratado
        AguardandoPagamento { .. } => ✓ tratado
        Pago { .. }                => ✓ tratado
        EmPreparacao { .. }        => ✓ tratado
        Enviado { .. }             => ✓ tratado
        Entregue { .. }            => ✓ tratado
        Cancelado { .. }           => ✓ tratado
        // Se adicionar novo estado, compilador EXIGE tratamento!
    }

Exemplo do Mundo Real

Uma máquina de estados para conexão TCP simplificada:

/// Estados de uma conexão TCP
#[derive(Debug)]
enum EstadoTCP {
    Fechada,
    Ouvindo { porta: u16 },
    SynEnviado { endereco: String },
    SynRecebido { endereco_remoto: String },
    Estabelecida {
        endereco_remoto: String,
        bytes_enviados: u64,
        bytes_recebidos: u64,
    },
    FinWait { motivo: String },
    Encerrada,
}

struct ConexaoTCP {
    estado: EstadoTCP,
    log: Vec<String>,
}

impl ConexaoTCP {
    fn nova() -> Self {
        ConexaoTCP {
            estado: EstadoTCP::Fechada,
            log: Vec::new(),
        }
    }

    fn ouvir(&mut self, porta: u16) -> Result<(), String> {
        match &self.estado {
            EstadoTCP::Fechada => {
                self.estado = EstadoTCP::Ouvindo { porta };
                self.log.push(format!("Ouvindo na porta {}", porta));
                Ok(())
            }
            _ => Err(format!("Não é possível ouvir no estado {:?}", self.estado)),
        }
    }

    fn conectar(&mut self, endereco: &str) -> Result<(), String> {
        match &self.estado {
            EstadoTCP::Fechada => {
                self.estado = EstadoTCP::SynEnviado {
                    endereco: endereco.to_string(),
                };
                self.log.push(format!("SYN enviado para {}", endereco));
                Ok(())
            }
            _ => Err(format!("Não é possível conectar no estado {:?}", self.estado)),
        }
    }

    fn aceitar_conexao(&mut self, endereco_remoto: &str) -> Result<(), String> {
        match &self.estado {
            EstadoTCP::Ouvindo { .. } => {
                self.estado = EstadoTCP::SynRecebido {
                    endereco_remoto: endereco_remoto.to_string(),
                };
                self.log.push(format!("SYN recebido de {}", endereco_remoto));
                Ok(())
            }
            _ => Err("Não está ouvindo".to_string()),
        }
    }

    fn estabelecer(&mut self) -> Result<(), String> {
        match &self.estado {
            EstadoTCP::SynEnviado { endereco } | EstadoTCP::SynRecebido { endereco_remoto: endereco } => {
                let end = endereco.clone();
                self.estado = EstadoTCP::Estabelecida {
                    endereco_remoto: end.clone(),
                    bytes_enviados: 0,
                    bytes_recebidos: 0,
                };
                self.log.push(format!("Conexão estabelecida com {}", end));
                Ok(())
            }
            _ => Err("Handshake não iniciado".to_string()),
        }
    }

    fn enviar_dados(&mut self, dados: &[u8]) -> Result<u64, String> {
        match &mut self.estado {
            EstadoTCP::Estabelecida { bytes_enviados, .. } => {
                *bytes_enviados += dados.len() as u64;
                self.log.push(format!("{} bytes enviados", dados.len()));
                Ok(dados.len() as u64)
            }
            _ => Err("Conexão não estabelecida".to_string()),
        }
    }

    fn fechar(&mut self, motivo: &str) -> Result<(), String> {
        match &self.estado {
            EstadoTCP::Estabelecida { .. } => {
                self.estado = EstadoTCP::FinWait {
                    motivo: motivo.to_string(),
                };
                self.log.push(format!("Fechando: {}", motivo));
                Ok(())
            }
            EstadoTCP::FinWait { .. } => {
                self.estado = EstadoTCP::Encerrada;
                self.log.push("Conexão encerrada".to_string());
                Ok(())
            }
            _ => Err("Nada para fechar".to_string()),
        }
    }
}

fn main() {
    let mut conn = ConexaoTCP::nova();

    conn.conectar("192.168.1.100:8080").unwrap();
    conn.estabelecer().unwrap();
    conn.enviar_dados(b"GET / HTTP/1.1\r\n").unwrap();
    conn.fechar("Transferência completa").unwrap();
    conn.fechar("FIN-ACK").unwrap();

    println!("Log da conexão:");
    for entrada in &conn.log {
        println!("  {}", entrada);
    }
}

Quando Usar

  • Comportamento dependente de estado: Quando o objeto se comporta diferentemente conforme o estado
  • Transições controladas: Quando nem toda mudança de estado é válida
  • Histórico de transições: Quando é importante registrar a evolução do estado
  • Dados específicos por estado: Quando cada estado carrega informações diferentes (enum data)
  • Segurança em tempo de compilação: Match exaustivo previne estados esquecidos

Quando NÃO Usar

  • Poucos estados simples: Se há apenas 2-3 estados com comportamento idêntico, um bool ou enum simples basta
  • Estados sem transições controladas: Se qualquer estado pode ir para qualquer outro, o padrão é overhead desnecessário
  • Estado global compartilhado: Se múltiplas partes do sistema modificam o estado concorrentemente, considere channels ou actors
  • Máquinas de estado configuráveis: Se as transições são definidas em runtime (via config), considere tabelas de transição

Variações em Rust

Type-State Pattern (estados como tipos)

Para segurança máxima em tempo de compilação:

/// Marcadores de estado (zero-size types)
struct Rascunho;
struct EmRevisao;
struct Publicado;

/// Artigo cujo estado é um parâmetro de tipo
struct Artigo<Estado> {
    titulo: String,
    conteudo: String,
    _estado: std::marker::PhantomData<Estado>,
}

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

    fn editar(&mut self, conteudo: &str) {
        self.conteudo = conteudo.to_string();
    }

    /// Envia para revisão — consome o rascunho, retorna em revisão
    fn enviar_para_revisao(self) -> Artigo<EmRevisao> {
        Artigo {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: std::marker::PhantomData,
        }
    }
}

impl Artigo<EmRevisao> {
    fn aprovar(self) -> Artigo<Publicado> {
        Artigo {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: std::marker::PhantomData,
        }
    }

    fn rejeitar(self) -> Artigo<Rascunho> {
        Artigo {
            titulo: self.titulo,
            conteudo: self.conteudo,
            _estado: std::marker::PhantomData,
        }
    }
}

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

fn main() {
    let mut artigo = Artigo::<Rascunho>::novo("Rust é Incrível");
    artigo.editar("Rust combina segurança e performance...");

    // Transição de tipo: Rascunho -> EmRevisao
    let artigo = artigo.enviar_para_revisao();

    // Transição de tipo: EmRevisao -> Publicado
    let artigo = artigo.aprovar();

    println!("URL: {}", artigo.url());

    // ERRO DE COMPILAÇÃO: Publicado não tem editar()
    // artigo.editar("tentativa"); // não compila!
}

Padrões Relacionados

  • Strategy: Strategy troca algoritmos; State troca comportamento baseado em estado interno
  • Type-State: Versão em tempo de compilação do State Pattern
  • Command: Comandos podem gatilhar transições de estado
  • Observer: Observadores podem ser notificados de mudanças de estado

Conclusão

O State Pattern em Rust se beneficia enormemente do sistema de enums com dados associados e do pattern matching exaustivo. Diferente de linguagens OO onde estados são classes separadas com herança, em Rust cada estado é uma variante de enum que pode carregar dados específicos. O compilador garante que todo estado seja tratado em cada match, eliminando uma classe inteira de bugs em tempo de compilação. Para segurança máxima, o Type-State Pattern (visto na seção de Variações) codifica estados como tipos, tornando transições inválidas literalmente impossíveis de expressar no código.