Rust Admin

Como Usar Pattern Matching com match e if let em Rust em Produção Já leu

Introdução ao Pattern Matching em Rust Pattern matching é um dos recursos mais poderosos de Rust. Ele permite que você desconstrua valores complexos de forma segura e expressiva, comparando-os contra padrões específicos. Diferente de simples comparações, pattern matching em Rust verifica exaustividade — o compilador garante que você tratou todos os casos possíveis, eliminando erros lógicos comuns. Rust oferece duas construções principais: para análise completa e para casos simples. Ambas funcionam com enums, tuplas, structs e até literais. A verdadeira magia está em como o compilador força você a ser exhaustivo e seguro, rejeitando código incompleto em tempo de compilação. A Expressão : Poder Total O é uma expressão que avalia um padrão contra múltiplos braços. Ao contrário de switches em outras linguagens, em Rust é exhaustivo — você deve cobrir todos os casos possíveis ou usar o padrão coringa rust enum Resultado { Sucesso(String), Erro(i32), Pendente, } fn processar(res: Resultado) -> String { match res { Resultado::Sucesso(mensagem) => {

Introdução ao Pattern Matching em Rust

Pattern matching é um dos recursos mais poderosos de Rust. Ele permite que você desconstrua valores complexos de forma segura e expressiva, comparando-os contra padrões específicos. Diferente de simples comparações, pattern matching em Rust verifica exaustividade — o compilador garante que você tratou todos os casos possíveis, eliminando erros lógicos comuns.

Rust oferece duas construções principais: match para análise completa e if let para casos simples. Ambas funcionam com enums, tuplas, structs e até literais. A verdadeira magia está em como o compilador força você a ser exhaustivo e seguro, rejeitando código incompleto em tempo de compilação.

A Expressão match: Poder Total

O match é uma expressão que avalia um padrão contra múltiplos braços. Ao contrário de switches em outras linguagens, match em Rust é exhaustivo — você deve cobrir todos os casos possíveis ou usar o padrão coringa _.

Exemplo Básico com Enum

enum Resultado {
    Sucesso(String),
    Erro(i32),
    Pendente,
}

fn processar(res: Resultado) -> String {
    match res {
        Resultado::Sucesso(mensagem) => {
            format!("✓ {}", mensagem)
        }
        Resultado::Erro(codigo) => {
            format!("✗ Erro {}", codigo)
        }
        Resultado::Pendente => String::from("⏳ Aguardando..."),
    }
}

fn main() {
    let resultado = Resultado::Sucesso("Dados carregados".to_string());
    println!("{}", processar(resultado)); // ✓ Dados carregados
}

Neste exemplo, cada variante do enum é tratada explicitamente. Se você esquecer um caso, o compilador recusa compilar o código. A desconstrução Sucesso(mensagem) extrai o valor interno automaticamente.

Padrões Avançados

O match suporta padrões complexos com guards (condições extras) e desestruturação profunda:

fn analisar_ponto(ponto: (i32, i32)) -> &'static str {
    match ponto {
        (0, 0) => "Origem",
        (0, y) if y > 0 => "Eixo Y positivo",
        (0, _) => "Eixo Y negativo",
        (x, 0) if x > 0 => "Eixo X positivo",
        (x, 0) => "Eixo X negativo",
        (x, y) if x == y => "Diagonal principal",
        (x, y) if x == -y => "Diagonal secundária",
        _ => "Ponto genérico",
    }
}

fn main() {
    println!("{}", analisar_ponto((0, 5)));    // Eixo Y positivo
    println!("{}", analisar_ponto((3, 3)));    // Diagonal principal
    println!("{}", analisar_ponto((4, 2)));    // Ponto genérico
}

Os guards com if permitem condições extras além do padrão estrutural. O padrão _ captura qualquer valor sem usá-lo — perfeito para casos que você não precisa processar.

if let: Simplicidade para Casos Únicos

Quando você só se importa com um padrão específico, if let oferece sintaxe mais clara e concisa. Ele é açúcar sintático sobre match com dois braços: um padrão que interessa e _ implícito para o resto.

Quando Usar if let

enum Cargo {
    Presidente(String),
    Desenvolvedor(String),
    Indefinido,
}

fn verificar_desenvolvedor(cargo: Cargo) {
    if let Cargo::Desenvolvedor(nome) = cargo {
        println!("Desenvolvedor: {}", nome);
    }
}

fn main() {
    let meu_cargo = Cargo::Desenvolvedor("Alice".to_string());
    verificar_desenvolvedor(meu_cargo); // Desenvolvedor: Alice
}

A vantagem aqui é legibilidade. Se você só precisa agir quando o cargo é desenvolvedor, if let é mais direto que match com vários braços vazios.

Combinando com else if let

Para múltiplas condições correlatas, encadeie if let:

fn classificar_valor(valor: Option<i32>) {
    if let Some(n) = valor {
        if n > 100 {
            println!("Grande: {}", n);
        } else {
            println!("Pequeno: {}", n);
        }
    } else {
        println!("Sem valor");
    }
}

fn main() {
    classificar_valor(Some(150)); // Grande: 150
    classificar_valor(None);       // Sem valor
}

Alternativamente, use else if let para sintaxe mais plana:

fn classificar_resultado(res: Result<i32, String>) {
    if let Ok(num) = res {
        println!("Sucesso: {}", num);
    } else if let Err(erro) = res {
        println!("Erro: {}", erro);
    }
}

Padrões Avançados e Desestruturação

Rust permite desestruturação profunda em structs e enums aninhados, tornando code muito expressivo sem variáveis intermediárias.

Desestruturação de Structs

struct Usuario {
    nome: String,
    idade: u32,
    email: Option<String>,
}

fn processar_usuario(usuario: Usuario) {
    match usuario {
        Usuario { nome, idade: 18..=65, email: Some(e) } => {
            println!("{} ({}) - {}", nome, idade, e);
        }
        Usuario { nome, idade: 18..=65, email: None } => {
            println!("{} ({}) - sem email", nome, idade);
        }
        Usuario { nome, idade, .. } => {
            println!("{} - fora da faixa etária ({})", nome, idade);
        }
    }
}

fn main() {
    let user = Usuario {
        nome: "Bob".to_string(),
        idade: 30,
        email: Some("bob@example.com".to_string()),
    };
    processar_usuario(user);
}

Os ranges 18..=65 e o coringa .. para ignorar campos desnecessários tornam o padrão altamente legível. O compilador valida que todos os campos necessários são cobertos.

Ranges e Literais

fn descrever_dia(dia: u32) -> &'static str {
    match dia {
        1 | 3 | 5 | 7 | 9 | 11 => "Mês com 31 dias",
        4 | 6 | 9 | 11 => "Mês com 30 dias",
        2 => "Fevereiro",
        _ => "Dia inválido",
    }
}

fn categoria_idade(idade: u32) -> &'static str {
    match idade {
        0..=12 => "Criança",
        13..=19 => "Adolescente",
        20..=59 => "Adulto",
        60.. => "Idoso",
        _ => unreachable!(),
    }
}

fn main() {
    println!("{}", categoria_idade(25)); // Adulto
}

Padrões de múltiplas alternativas com | e ranges com .. cobrem casos comuns sem verbosidade. O unreachable!() marca código que nunca deve ser alcançado, ajudando o compilador com análise de fluxo.

Conclusão

Pattern matching é fundamental em Rust porque garante segurança em tempo de compilação. O compilador força você a considerar todos os cenários, eliminando bugs antes que ocorram. Use match para análise exaustiva e múltiplos padrões; use if let para casos únicos onde você só se importa com um resultado específico.

Desestruturação reduz código redundante — em vez de acessar campos manualmente, deixe o padrão extrair valores diretamente nas variáveis que você precisa. Guards e ranges tornam lógica complexa expressa elegantemente.

O compilador é seu aliado — rejeições iniciais parecem frustrantes, mas protegem você de horas de debugging. Abraçe a natureza exhaustiva do pattern matching e escreva código que é ao mesmo tempo seguro e legível.

Referências


Artigos relacionados