Pular para o conteúdo

If Let e While Let em Rust

Você já usou o match para tratar Option e Result. Na maioria das vezes, porém, você só se importa de verdade com um padrão e quer ignorar todo o resto. Escrever um match completo com um braço _ => {} só para não fazer nada funciona, mas é cerimônia demais para a situação. O if let é o atalho.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("A idade é {value}"),
_ => {}, // na verdade, este caso não nos interessa
}
}

Esse _ => {} não faz nada além de satisfazer a regra do match de que “todo caso precisa ser tratado”. Se você só quer agir quando for Some, isso é código demais para uma ideia tão simples.

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("A idade é {value}");
}
}

Saída:

A idade é 30
▶ Experimente você mesmo

Leia assim: “se age combinar com o padrão Some(value), execute este bloco, com value valendo o que estava dentro”. Se age for None, o bloco é simplesmente pulado: nenhum erro, nada impresso.

O if let pode ter um else, igualzinho a um if comum:

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("A idade é {value}");
} else {
println!("Nenhuma idade informada");
}
}
▶ Experimente você mesmo

Funciona do mesmo jeito com Result, que você conheceu na página de Result:

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("Convertido: {number}");
} else {
println!("Não foi possível converter");
}
}
▶ Experimente você mesmo

Assim como o if tem o else if, o if let pode ser seguido de um else if let. O Rust tenta cada padrão por vez e executa o primeiro bloco que combinar. Isso é útil quando você tem alguns lugares onde procurar um valor, em ordem de preferência:

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Ferris Crab");
if let Some(nick) = nickname {
println!("Oi, {nick}!");
} else if let Some(name) = full_name {
println!("Olá, {name}.");
} else {
println!("Olá, desconhecido.");
}
}

Saída:

Olá, Ferris Crab.
▶ Experimente você mesmo

O nickname (apelido) é None, então o primeiro bloco é pulado. O full_name (nome completo) combina com Some(name), então esse bloco é executado, e o else final nunca é alcançado. Você também pode misturar uma condição comum, como else if is_admin { ... }, em qualquer ponto da cadeia.

Cada variável só existe dentro do próprio bloco: nick não pode ser usada no segundo bloco, e name não pode ser usada depois que a cadeia termina. Se você precisa do valor desempacotado no resto do código, veja o let-else na página de Pattern matching.

O while let aplica a mesma ideia a um loop: continue repetindo enquanto um valor combinar com um padrão. Ele é especialmente útil para esvaziar uma coleção:

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

Saída:

3
2
1
▶ Experimente você mesmo

O .pop() retorna Some(valor) enquanto ainda sobrar alguma coisa, e None quando o vetor fica vazio. Nesse momento, o while let para o loop automaticamente. Não é preciso verificar o tamanho na mão.

Use match quando você realmente precisa tratar vários casos de formas diferentes. Prefira o if let quando só existe um padrão que interessa e todo o resto pode ser ignorado. Ele não substitui o match: é só um jeito mais curto de escrever o caso comum de “só me importa um braço”.

Comece com let mut numbers = vec![4, 8, 15]; e um total acumulado igual a 0. Use while let com .pop() para tirar os números do final, um de cada vez, somar cada um ao total e imprimir o número e o novo total. Dica: o total precisa de mut, e total += n soma ao valor dele.

Ver solução
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("tirei {n}, o total agora é {total}");
}
}

Saída:

tirei 15, o total agora é 15
tirei 8, o total agora é 23
tirei 4, o total agora é 27
▶ Experimente você mesmo

O .pop() tira do final, e é por isso que o 15 sai primeiro.

Guarde let input = "17";. Se ele puder ser convertido em u32, imprima se essa idade é de um adulto (18 ou mais) ou de um menor. Se não puder, imprima que não é um número. Experimente também trocar input por "abc". Dica: coloque um if / else comum dentro do bloco do if let.

Ver solução
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age} anos: adulto");
} else {
println!("{age} anos: menor de idade");
}
} else {
println!("'{input}' não é um número");
}
}

Saída:

17 anos: menor de idade
▶ Experimente você mesmo

Com input = "abc", a conversão falha, o else de fora é executado e o programa imprime 'abc' não é um número.

  • if let Padrão = valor { ... } executa o bloco só se valor combinar com Padrão; todo o resto é pulado em silêncio.
  • Adicione um else para tratar o caso que não combina, como em um if comum, e encadeie vários padrões com else if let.
  • while let Padrão = valor { ... } repete enquanto o valor continuar combinando, o que é útil para esvaziar uma coleção com .pop().
  • Use if let/while let quando só um padrão importa; use match quando precisar tratar vários casos de formas diferentes.

Teste o que aprendeu

1. O que acontece quando if let Some(value) = age é executado e age é None?

2. Quando você deve preferir match em vez de if let?

3. Em while let Some(top) = stack.pop() { ... }, quando o loop para?

Pontuação: 0 / 3