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.