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
- The Rust Programming Language - Match Control Flow — Capítulo oficial do livro de Rust sobre a expressão match, com exemplos básicos e avançados.
- Rust Reference - Match Expressions — Documentação de referência detalhada sobre a sintaxe e semântica do match em Rust.
- Rust by Example - Match — Tutoriais práticos com exemplos de código para dominar pattern matching.
- Patterns and Matching - Rust Book — Capítulo completo sobre padrões em Rust, incluindo refutabilidade, binding e desestruturação.
- Rust Patterns: Match Guards and Binding — Vídeo tutorial explicando guardas, bindings com @ e padrões avançados em match.
- Effective Rust - Pattern Matching — Guia de boas práticas para usar pattern matching de forma eficiente e idiomática em Rust.