Pattern matching com match

1. Introdução ao match: O Coração do Controle de Fluxo em Rust

1.1. Sintaxe básica: match como expressão, braços e padrões

O match em Rust é muito mais que um simples switch-case. Ele é uma expressão que pode retornar valores, permitindo que cada braço defina um padrão a ser comparado com o valor de entrada.

let numero = 3;

let mensagem = match numero {
    1 => "um",
    2 => "dois",
    3 => "três",
    _ => "outro",
};

println!("{}", mensagem); // "três"

1.2. Diferença fundamental de switch em outras linguagens

Diferente de linguagens como C ou Java, o match em Rust:
- É exaustivo: obriga a cobrir todas as possibilidades
- Não precisa de break: cada braço é automaticamente isolado
- Pode desestruturar: extrai dados internos de estruturas complexas

1.3. O papel do match na segurança de tipos

O compilador verifica se todos os casos foram tratados, prevenindo erros comuns como esquecer de tratar um valor nulo ou uma variante de enum.

enum Opcao {
    Sim,
    Nao,
    Talvez,
}

fn processar(op: Opcao) -> &'static str {
    match op {
        Opcao::Sim => "confirmado",
        Opcao::Nao => "negado",
        Opcao::Talvez => "indefinido",
        // Se esquecermos Talvez, o compilador avisa!
    }
}

2. Padrões Fundamentais: Literais, Variáveis e _

2.1. Correspondência com literais

match caractere {
    'a' => println!("vogal a"),
    'b' | 'c' => println!("consoante b ou c"),
    '0'..='9' => println!("dígito"),
    _ => println!("outro caractere"),
}

2.2. Captura de valores com variáveis

let valor: Option<i32> = Some(42);

match valor {
    Some(x) => println!("Valor capturado: {}", x),
    None => println!("Nenhum valor"),
}

2.3. Uso de _ e .. para ignorar partes

let coordenada = (10, 20, 30);

match coordenada {
    (x, _, _) => println!("x = {}", x), // ignora y e z
    _ => unreachable!(),
}

let lista = [1, 2, 3, 4, 5];
match lista {
    [primeiro, .., ultimo] => println!("{} ... {}", primeiro, ultimo),
    _ => (),
}

3. Desestruturação em match: Extraindo Dados de Estruturas

3.1. Desestruturação de structs e tuplas

struct Pessoa {
    nome: String,
    idade: u8,
}

let pessoa = Pessoa {
    nome: String::from("Ana"),
    idade: 30,
};

match pessoa {
    Pessoa { nome, idade: 30 } => println!("{} tem 30 anos", nome),
    Pessoa { nome, .. } => println!("{} não tem 30 anos", nome),
}

3.2. Desestruturação de enums (Option e Result)

let resultado: Result<i32, String> = Ok(100);

match resultado {
    Ok(valor) => println!("Sucesso: {}", valor),
    Err(erro) => println!("Erro: {}", erro),
}

3.3. Aninhamento de desestruturação

enum Mensagem {
    Texto(String),
    Coordenadas { x: i32, y: i32 },
}

let msg = Mensagem::Coordenadas { x: 10, y: 20 };

match msg {
    Mensagem::Texto(s) => println!("Texto: {}", s),
    Mensagem::Coordenadas { x, y } => {
        println!("Ponto: ({}, {})", x, y);
    }
}

4. Condições com Guardas (match Guards)

4.1. Sintaxe de if dentro de braços do match

Guardas permitem adicionar condições booleanas além do padrão.

let numero = Some(7);

match numero {
    Some(x) if x % 2 == 0 => println!("{} é par", x),
    Some(x) => println!("{} é ímpar", x),
    None => println!("nenhum número"),
}

4.2. Combinando padrões com condições arbitrárias

let par = (10, 5);

match par {
    (x, y) if x > y => println!("{} > {}", x, y),
    (x, y) if x < y => println!("{} < {}", x, y),
    _ => println!("iguais"),
}

4.3. Limitações e boas práticas

Guardas não podem usar variáveis vinculadas no padrão de forma mutável. Prefira guardas simples e evite lógica complexa dentro delas.

// Ruim: lógica complexa na guarda
match valor {
    Some(x) if (x > 10 && x < 20) || x == 100 => { /* ... */ }
    _ => ()
}

// Melhor: extrair para função
fn condicao_especial(x: i32) -> bool {
    (x > 10 && x < 20) || x == 100
}

match valor {
    Some(x) if condicao_especial(x) => { /* ... */ }
    _ => ()
}

5. Padrões Avançados: Or, Range e Binding

5.1. Padrões | (or) para múltiplas alternativas

let cor = "azul";

match cor {
    "vermelho" | "azul" | "verde" => println!("cor primária"),
    _ => println!("outra cor"),
}

5.2. Padrões de intervalo (..=) para ranges inclusivos

let nota = 85;

match nota {
    90..=100 => println!("A"),
    80..=89 => println!("B"),
    70..=79 => println!("C"),
    0..=69 => println!("F"),
    _ => unreachable!(),
}

5.3. @ bindings: vinculando valores a variáveis

O operador @ permite capturar o valor que corresponde a um padrão.

enum Id {
    Numero(i32),
    Texto(String),
}

let id = Id::Numero(42);

match id {
    Id::Numero(n @ 0..=100) => println!("Número pequeno: {}", n),
    Id::Numero(n) => println!("Número grande: {}", n),
    Id::Texto(s) => println!("Texto: {}", s),
}

6. Exaustividade e Irrefutabilidade

6.1. Obrigação de cobrir todos os casos

O compilador Rust garante que todos os casos sejam tratados, evitando erros em tempo de execução.

enum Status {
    Ativo,
    Inativo,
    Pendente,
}

fn mensagem_status(s: Status) -> &'static str {
    match s {
        Status::Ativo => "funcionando",
        Status::Inativo => "parado",
        // Se esquecermos Pendente, erro de compilação!
    }
}

6.2. Padrões refutáveis vs. irrefutáveis

  • Irrefutável: sempre corresponde (ex: let x = 5;)
  • Refutável: pode não corresponder (ex: if let Some(x) = valor)
// Irrefutável: sempre funciona
let x = 5;
let (a, b) = (1, 2);

// Refutável: precisa de match ou if let
let valor = Some(3);
if let Some(x) = valor {
    println!("{}", x);
}

6.3. Estratégias para enums em evolução

Use _ para cobrir variantes futuras, mas com cuidado:

match status {
    Status::Ativo => "ativo",
    Status::Inativo => "inativo",
    _ => "desconhecido", // Seguro para novas variantes
}

7. match com Referências e Mutabilidade

7.1. Correspondência com referências

let valor = &Some(42);

match valor {
    &Some(x) => println!("{}", x),
    &None => println!("nada"),
}

7.2. O operador ref e ref mut

Use ref para obter referências em vez de mover valores:

let nome = String::from("Rust");

match nome {
    ref n => println!("{}", n), // n é &String
}
// nome ainda é válido aqui!

// Com mutabilidade:
let mut nome = String::from("Rust");
match nome {
    ref mut n => n.push_str(" é incrível"),
}
println!("{}", nome); // "Rust é incrível"

7.3. Implicações de ownership

Match move valores por padrão. Use ref para evitar mover:

let opt = Some(String::from("dado"));

match opt {
    Some(s) => println!("{}", s), // s é movido para cá
    None => (),
}
// opt não pode mais ser usado aqui!

// Solução com ref:
match &opt {
    Some(s) => println!("{}", s), // s é &String
    None => (),
}
// opt ainda é válido!

8. Padrões e Performance: Otimizações e Boas Práticas

8.1. Como o compilador Rust otimiza match

O compilador pode gerar:
- Tabelas de salto: para inteiros e chars em ranges contíguos
- Busca binária: para padrões ordenados
- Árvores de decisão: para padrões complexos

8.2. Ordem dos braços: impacto na legibilidade

Coloque os casos mais específicos primeiro e os genéricos (como _) por último:

match valor {
    0 => println!("zero"),
    1..=10 => println!("pequeno"),
    _ => println!("grande"), // curinga no final
}

8.3. Quando evitar match

Use alternativas mais concisas quando apropriado:

// matches! macro para verificação simples
if matches!(valor, Some(x) if x > 10) {
    println!("valor grande");
}

// Closures com Option/Result
let resultado = valor.map(|x| x * 2).unwrap_or(0);

Referências