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

  1. Abuso do .unwrap() em Código de Produção:
    • Risco: Chamar resultado.unwrap() faz a aplicação entrar em panic imediato se o valor for None ou Err.
    • Correção: Em código corporativo, use o operador ?, unwrap_or(), unwrap_or_else() ou match.
  2. Match Não Exaustivo (E0004): O compilador rejeitará qualquer match que 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; };