if let e while let
Nesta aula, exploramos os constructos if let e while let em Rust, que são açúcares sintáticos para simplificar o uso de match em casos específicos. Aprendemos quando utilizá-los, como combiná-los com else e vimos exemplos práticos de sua aplicação.
Em Rust, os padrões (patterns) são uma ferramenta poderosa para desestruturar dados e controlar o fluxo do programa. Embora o match seja completo e flexível, em situações onde nos importamos com apenas um ou alguns padrões, ele pode se tornar verboso. Para esses casos, Rust oferece os constructos if let e while let, que são açúcares sintáticos que simplificam o código, mantendo a segurança e a expressividade.
Nesta aula, vamos entender como esses constructos funcionam, quando são apropriados e como combiná-los com else para lidar com casos não correspondentes. Veremos exemplos práticos que demonstram sua utilidade no dia a dia da programação Rust.
Açúcar para match
O if let é uma forma concisa de escrever um match que corresponde a apenas um padrão e ignora todos os outros. Em vez de escrever um bloco match com um braço e um curinga (_), podemos usar if let para tornar a intenção mais clara e o código mais enxuto.
Considere o exemplo com Option<T>. Sem if let, para verificar se um valor é Some e extrair o valor interno, faríamos:
let valor = Some(42);
match valor {
Some(x) => println!("O valor é {}", x),
None => (), // ignoramos o caso None
}
Com if let, o mesmo código fica mais direto:
if let Some(x) = valor {
println!("O valor é {}", x);
}
A sintaxe geral é:
if let padrao = expressao {
// código se o padrão corresponde
}
O while let funciona de forma análoga, mas em um loop: enquanto o padrão corresponder, o bloco é executado. É útil para consumir iteradores ou processar dados até que uma condição de falha ocorra.
Por exemplo, para percorrer uma Vec<Option<i32>> e extrair apenas os valores Some:
let mut valores = vec![Some(1), None, Some(2), None];
while let Some(Some(x)) = valores.pop() {
println!("Encontrado: {}", x);
}
Aqui, valores.pop() retorna Option<Option<i32>>. O padrão Some(Some(x)) corresponde apenas se houver um elemento e ele for Some. O loop continua enquanto isso for verdade.
Quando usar
Use if let quando você está interessado em apenas uma variante de um enum ou em um único padrão, e deseja ignorar todos os outros. É especialmente útil para tipos como Option<T> e Result<T, E>, onde normalmente nos importamos com o caso de sucesso e ignoramos o erro (ou vice-versa).
Evite usar if let quando você precisa lidar com múltiplos padrões distintos. Nesse caso, match é mais adequado, pois permite tratar cada braço explicitamente. Além disso, se você precisa de uma cláusula else que execute código quando o padrão não corresponde, if let com else é uma boa opção.
Quanto ao while let, é ideal para loops onde a condição de continuação é baseada em um padrão. Por exemplo, ao consumir um iterador que retorna Option (como em next()), ou ao processar mensagens de um canal.
Em geral, prefira if let e while let quando eles tornarem o código mais legível e conciso. Se o match for mais claro, use-o.
Combinando com else
O if let pode ser combinado com um else para executar código quando o padrão não corresponde. Isso é equivalente a um match com um braço curinga que executa uma ação.
Exemplo com Result:
let resultado: Result<i32, &str> = Err("falha");
if let Ok(valor) = resultado {
println!("Sucesso: {}", valor);
} else {
println!("Falha");
}
Isso é equivalente a:
match resultado {
Ok(valor) => println!("Sucesso: {}", valor),
Err(_) => println!("Falha"),
}
O while let não possui uma cláusula else intrínseca, mas você pode usar um if dentro do loop para verificar condições adicionais, ou simplesmente deixar o loop terminar quando o padrão não corresponder.
Também é possível encadear múltiplos if let com else if, embora isso possa se tornar confuso. Nesses casos, considere usar match.
Exemplos
Vamos ver alguns exemplos práticos que ilustram o uso de if let e while let.
Exemplo 1: Extraindo valor de Option
let nome: Option<&str> = Some("Alice");
if let Some(n) = nome {
println!("Olá, {}!", n);
}
Exemplo 2: Tratando erro com Result
let arquivo: Result<String, std::io::Error> = std::fs::read_to_string("config.txt");
if let Ok(conteudo) = arquivo {
println!("Conteúdo: {}", conteudo);
} else {
println!("Não foi possível ler o arquivo");
}
Exemplo 3: Loop com while let em um iterador
let mut iter = (0..5).into_iter();
while let Some(x) = iter.next() {
println!("Número: {}", x);
}
Exemplo 4: Desestruturando um enum personalizado
enum Mensagem {
Texto(String),
Sair,
}
fn processar(msg: Mensagem) {
if let Mensagem::Texto(t) = msg {
println!("Mensagem: {}", t);
} else {
println!("Comando de saída");
}
}
fn main() {
processar(Mensagem::Texto("Olá".to_string()));
processar(Mensagem::Sair);
}
Exemplo 5: while let com padrões complexos
let mut pilha = vec![(1, "a"), (2, "b"), (3, "c")];
while let Some((num, letra)) = pilha.pop() {
println!("{}: {}", num, letra);
}
Boas práticas
Use if let e while let para simplificar o código quando você só se importa com um padrão. Evite aninhar muitos if let; prefira match para múltiplos padrões. Lembre-se de que if let pode ser combinado com else e else if let, mas mantenha a legibilidade.
Referências
- The Rust Programming Language - if let
- Rust by Example - if let
- Rust by Example - while let
- Rust Reference - if let expressions
- Rust Reference - while let loops
- std::option documentation
Exercícios
-
Escreva uma função que recebe um
Option<i32>e imprime o valor se forSome, caso contrário imprime "Nada". Useif let.✓ Resposta:fn imprimir_opcao(valor: Option<i32>) { if let Some(x) = valor { println!("{}", x); } else { println!("Nada"); } } -
Crie um loop que itere sobre um vetor de
Result<u32, String>e imprima apenas os valores de sucesso, usandowhile let.✓ Resposta:let resultados = vec![Ok(10), Err("erro".to_string()), Ok(20)]; let mut iter = resultados.into_iter(); while let Some(Ok(valor)) = iter.next() { println!("Sucesso: {}", valor); } -
Dado um enum
Animal { Cachorro(String), Gato(u8) }, escreva uma função que recebe umAnimale imprime o nome se forCachorro, ou a idade se forGato. Useif letcomelse.✓ Resposta:enum Animal { Cachorro(String), Gato(u8), } fn descrever(animal: Animal) { if let Animal::Cachorro(nome) = animal { println!("Cachorro: {}", nome); } else if let Animal::Gato(idade) = animal { println!("Gato: {} anos", idade); } } -
Use
while letpara consumir um iterador dechare imprimir cada caractere até encontrar um espaço.✓ Resposta:let frase = "Olá mundo".chars(); let mut iter = frase; while let Some(c) = iter.next() { if c == ' ' { break; } println!("{}", c); } -
Escreva um programa que lê linhas de um arquivo (simule com um vetor de
Option<String>) e imprime cada linha, parando quando encontrarNone. Usewhile let.✓ Resposta:let linhas = vec![Some("linha1".to_string()), Some("linha2".to_string()), None, Some("linha3".to_string())]; let mut iter = linhas.into_iter(); while let Some(Some(linha)) = iter.next() { println!("{}", linha); }