Capítulo 10: Enums, Option, Result e Pattern Matching com match/if let
Capítulo 10: Enums, Option, Result e Pattern Matching com match/if let
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual de Tipos Algébricos, Option, Result e Pattern Matching
flowchart TD
ADT["Tipos Algébricos de Dados (Enums com Payload Heterogêneo)"] --> Casamento["Mecanismo de Desestruturação"]
subgraph SYSTEM_SAFETY["Eliminação de Falhas Clássicas"]
Opt["Option<T>: Some(T) | None (Zero Null Pointers)"]
Res["Result<T, E>: Ok(T) | Err(E) (Zero Exceções Ocultas)"]
end
ADT --- SYSTEM_SAFETY
subgraph MATCH_ENGINE["Controle de Fluxo e Casamento de Padrões"]
MatchExaustivo["match expressao { ... }: Exaustividade Mandatória"]
IfLet["if let Some(x) = expr: Tratamento Sucinto de Variante"]
LetElse["let Some(x) = expr else { return }: Desembrulho com Guarda"]
Propagador["Operador ?: Propagação Automática de Erros"]
end
SYSTEM_SAFETY --> MATCH_ENGINE
MATCH_ENGINE --> CodigoRobusto["Código Corporativo Sem Panics e Totalmente Previsível"]
style ADT fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style SYSTEM_SAFETY fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style MATCH_ENGINE fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style CodigoRobusto fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. Tipos Algébricos de Dados (ADTs) e o Fim do Ponteiro Nulo
Em linguagens procedurais clássicas como C, enums são apenas inteiros com nomes amigáveis. No Rust, os enums são Tipos Soma (Sum Types ou Algebraic Data Types):
- Cada variante de um enum pode conter dados com formatos, tipos e tamanhos completamente diferentes.
- A memória ocupada por um enum é exatamente o tamanho de sua maior variante mais um byte de discriminador (tag/tag byte), com alinhamento de memória eficiente.
1.1 O Fim do “Billion-Dollar Mistake”
Sir Tony Hoare, criador do ponteiro nulo em 1965, chamou a invenção do null de seu “erro de um bilhão de dólares”, responsável por inúmeras falhas de segurança e travamentos em produção (NullPointerException).
O Rust não possui ponteiro nulo. Quando um valor pode ou não estar presente, o modelo de tipos exige formalmente o enum genérico Option<T>:
enum Option<T> {
Some(T), // Contém um valor válido do tipo T
None, // Indica formalmente a ausência de valor
}
Como T e Option<T> são tipos distintos, você nunca pode usar um Option<T> acidentalmente como se fosse um valor válido sem desembrulhá-lo e tratar explicitamente o caso None!
⚙️ 2. Enums com Payloads Heterogêneos
Veja como modelar mensagens de rede de forma expressiva e segura:
#[derive(Debug)]
enum PacoteRede {
Ping, // Variante sem dados adicionais
Handshake(u64), // Variante contendo um timestamp numérico
Roteamento { origem: String, destino: String }, // Variante com struct anônima
PayloadDados(Vec<u8>), // Variante com buffer contíguo dinâmico
}
fn despachar_pacote(pacote: PacoteRede) {
// O compilador EXIGE que todas as variantes sejam tratadas!
match pacote {
PacoteRede::Ping => println!("Respondendo com PONG"),
PacoteRede::Handshake(ts) => println!("Handshake recebido no timestamp: {}", ts),
PacoteRede::Roteamento { origem, destino } => {
println!("Roteando tráfego de {} para {}", origem, destino);
}
PacoteRede::PayloadDados(bytes) => {
println!("Gravando payload de {} bytes no disco", bytes.len());
}
}
}
🛡️ 3. O Enum Result<T, E> e o Operador ?
Para operações que podem falhar (leitura de arquivos, conexões de rede, conversão de formatos), o Rust adota o enum Result<T, E>:
enum Result<T, E> {
Ok(T), // A operação obteve sucesso e gerou o dado T
Err(E), // A operação falhou com o erro E
}
3.1 Propagação Elegante com o Operador ?
Em vez de aninhar múltiplos blocos match para checar cada operação falível, o operador ? desembrulha o valor se for Ok(v) ou força um retorno antecipado da função caso seja Err(e):
use std::fs::File;
use std::io::{self, Read};
fn ler_arquivo_configuracao(caminho: &str) -> Result<String, io::Error> {
// Se File::open falhar, retorna o io::Error imediatamente!
let mut arquivo = File::open(caminho)?;
let mut conteudo = String::new();
// Se read_to_string falhar, propaga o erro para quem chamou
arquivo.read_to_string(&mut conteudo)?;
Ok(conteudo) // Retorno bem-sucedido embrulhado em Ok
}
🎯 4. Estruturas de Pattern Matching Modernas
4.1 match com Match Guards
Permite adicionar condições lógicas extras a um ramo de casamento:
let temperatura = Some(35);
match temperatura {
Some(t) if t > 40 => println!("Calor extremo! Ativando resfriamento."),
Some(t) if t < 0 => println!("Congelamento detectado! Ativando aquecedores."),
Some(t) => println!("Temperatura operacional estável: {}°C", t),
None => println!("Sensor desconectado."),
}
4.2 if let (Tratamento Conciso)
Quando você só se importa com uma única variante e deseja ignorar todas as outras:
let configuracao_cache: Option<u32> = Some(1024);
if let Some(tamanho) = configuracao_cache {
println!("Tamanho do cache configurado: {} MB", tamanho);
}
4.3 let else (Desembrulho com Guarda Antecipada)
Introduzido no Rust 1.65, o let else desembrulha o valor com sucesso ou executa um bloco divergente obrigatório (return, break ou panic):
fn processar_usuario(id_opcional: Option<u64>) {
// Desembrulha o ID ou sai imediatamente da função
let Some(id) = id_opcional else {
println!("Acesso anônimo rejeitado.");
return;
};
println!("Processando privilégios do usuário ID: {}", id);
}
⚖️ 5. Comparativo: Exceptions vs Result<T, E>
| Dimensão Técnica | Exceptions (Java, C#, Python) | Result<T, E> (Rust) |
|---|---|---|
| Visibilidade | Oculta na assinatura (pode explodir a qualquer momento) | 100% Explícita no Tipo de Retorno |
| Custo de Performance | Alto (criação e desenrolamento da Stack Trace) | Zero (Apenas retorno de enum em registrador/stack) |
| Tratamento Obrigatório | Opcional (fácil esquecer o try/catch) |
Obrigatório pelo Compilador (warning se ignorado) |
⚠️ 6. Armadilhas Comuns e Mensagens do Compilador
- Abuso do
.unwrap()em Código de Produção:- Risco: Chamar
resultado.unwrap()faz a aplicação entrar em panic imediato se o valor forNoneouErr. - Correção: Em código corporativo, use o operador
?,unwrap_or(),unwrap_or_else()oumatch.
- Risco: Chamar
- Match Não Exaustivo (E0004):
O compilador rejeitará qualquer
matchque não cubra todas as variantes possíveis do enum. Se desejar cobrir o resto, utilize o coringa_ => ....
🛡️ 7. Boas Práticas e Resiliência Corporativa
- Enums para Máquinas de Estado: Modele fluxos de negócio (como status de pedidos:
Criado,AguardandoPagamento,Enviado,Cancelado) com enums. O compilador garantirá que transições ilegais sejam detectadas em tempo de compilação.
📋 8. Cheat-Sheet: Resumo Executivo
// Option e Result
let valor: Option<i32> = Some(10);
let res: Result<i32, &str> = Ok(100);
// Pattern Matching
match res {
Ok(v) => println!("Sucesso: {}", v),
Err(e) => eprintln!("Erro: {}", e),
}
// if let & let else
if let Some(v) = valor { /* ... */ }
let Some(v) = valor else { return; };