Controle de fluxo em Rust
LidoEsta aula explora o controle de fluxo em Rust, mostrando como usar if/else como expressões, os laços loop, while e for, a possibilidade de retornar valores com break e o uso de labels para controle de loops aninhados. O conteúdo inclui exemplos práticos e exercícios para fixar cada conceito.
O controle de fluxo é essencial em qualquer linguagem de programação, e Rust oferece construções poderosas e expressivas. Nesta aula, você aprenderá a usar if/else como expressão, os laços loop, while e for, como retornar valores de loops com break e como controlar loops aninhados com labels. Vamos explorar cada tópico com detalhes e exemplos práticos.
Em Rust, o controle de fluxo não é apenas sobre decisões e repetição: muitas construções são expressões, o que significa que produzem valores que podem ser atribuídos ou usados em outras partes do código. Isso torna o código mais conciso e funcional. Vamos mergulhar em cada conceito.
if/else como expressão
Em Rust, o if não é uma declaração que apenas executa blocos; ele é uma expressão que pode retornar um valor. Isso é diferente de muitas linguagens, onde if é uma declaração. A sintaxe básica é:
let numero = 7;
if numero % 2 == 0 {
println!("par");
} else {
println!("ímpar");
}Como expressão, podemos atribuir o resultado de um if/else a uma variável. Cada bloco deve terminar com uma expressão sem ponto e vírgula, e os tipos de todas as ramificações devem ser compatíveis. Exemplo:
let condicao = true;
let valor = if condicao { 10 } else { 20 };
println!("valor = {}", valor); // valor = 10Também é possível usar else if para múltiplas condições, mas Rust não tem um switch tradicional; para múltiplas condições, você pode usar match (que veremos em outra aula) ou encadear else if.
let numero = 15;
if numero % 3 == 0 && numero % 5 == 0 {
println!("FizzBuzz");
} else if numero % 3 == 0 {
println!("Fizz");
} else if numero % 5 == 0 {
println!("Buzz");
} else {
println!("{}", numero);
}Lembre-se: em Rust, não há necessidade de parênteses ao redor da condição, mas as chaves são obrigatórias. A condição deve ser do tipo bool, e não há conversão automática de inteiros para booleanos.
loop, while e for
Rust fornece três tipos de laços: loop, while e for. Cada um tem seu propósito.
loop
O loop é um laço infinito que só termina quando você explicitamente usa break. É útil quando você precisa executar um bloco até que uma determinada condição seja satisfeita, mas a verificação pode ocorrer em qualquer ponto do bloco.
let mut contador = 0;
loop {
println!("contador = {}", contador);
contador += 1;
if contador == 5 {
break;
}
}O loop é a única construção que pode ser usada para criar um laço infinito deliberado, como em servidores ou listeners.
while
O while repete um bloco enquanto uma condição for verdadeira. A condição é verificada antes de cada iteração.
let mut numero = 3;
while numero != 0 {
println!("{}", numero);
numero -= 1;
}
println!("lançamento!");O while é útil quando o número de iterações não é conhecido antecipadamente, mas depende de uma condição que muda durante a execução.
for
O for é usado para iterar sobre coleções ou intervalos. É a forma mais comum de percorrer uma sequência de valores. Rust usa o conceito de iteradores, e o for é açúcar sintático para um loop sobre um iterador.
for i in 0..5 {
println!("i = {}", i);
}
// Imprime 0, 1, 2, 3, 4O intervalo 0..5 é exclusivo no final. Para incluir o 5, use 0..=5. O for também funciona com arrays, vetores e outros tipos que implementam a trait IntoIterator.
let nomes = ["Ana", "Bruno", "Carlos"];
for nome in nomes.iter() {
println!("Olá, {}", nome);
}break com valor
Em Rust, o break pode retornar um valor do loop. Isso é especialmente útil com loop, mas também pode ser usado com while e for. O valor do break se torna o valor da expressão do loop. Por exemplo:
let mut contador = 0;
let resultado = loop {
contador += 1;
if contador == 10 {
break contador * 2;
}
};
println!("resultado = {}", resultado); // resultado = 20Isso é útil quando você precisa calcular um valor dentro de um loop e sair com ele, evitando variáveis mutáveis extras. O tipo do valor de break deve ser consistente em todos os pontos de saída do loop.
Também é possível usar break com valor em while e for, embora seja menos comum. Em um while, o valor padrão do loop é () se não houver break com valor; se houver, o tipo deve ser o mesmo.
let mut x = 0;
let valor = while x < 5 {
x += 1;
if x == 3 {
break x * 10;
}
};
println!("valor = {:?}", valor); // valor = Some(30) ? Na verdade, o tipo é (i32,)? Vamos testarNa verdade, o while retorna () por padrão, e o break com valor não é permitido em while ou for? Vamos esclarecer: em Rust, break com valor só é permitido em loop, não em while ou for. Isso é uma restrição da linguagem. Portanto, o exemplo acima não compilaria. Para retornar um valor de um loop condicional, você deve usar loop com uma condição interna.
let mut x = 0;
let valor = loop {
x += 1;
if x < 5 {
if x == 3 {
break x * 10;
}
} else {
break 0; // ou outro valor padrão
}
};
println!("valor = {}", valor);Para simplificar, lembre-se: break com valor é uma característica exclusiva do loop.
Labels de loop
Quando você tem loops aninhados e precisa sair de um loop externo a partir do interno, você pode usar labels. Um label é um identificador seguido de dois pontos, colocado antes do loop. Para sair de um loop específico, use break 'label. Isso é útil para evitar o uso de variáveis booleanas de controle.
'externo: loop {
println!("Entrou no loop externo");
loop {
println!("Entrou no loop interno");
break 'externo; // Sai do loop externo
}
println!("Isto nunca será impresso");
}
println!("Saiu do loop externo");Labels também podem ser usados com continue para pular para a próxima iteração do loop rotulado. Por exemplo:
'externo: for i in 0..3 {
for j in 0..3 {
if i == 1 && j == 1 {
continue 'externo;
}
println!("i = {}, j = {}", i, j);
}
}
// Imprime:
// i = 0, j = 0
// i = 0, j = 1
// i = 0, j = 2
// i = 1, j = 0
// i = 2, j = 0
// i = 2, j = 1
// i = 2, j = 2Os labels são particularmente úteis em algoritmos de busca ou processamento de matrizes, onde você precisa interromper todas as iterações quando uma condição é encontrada.
Boas práticas e observações finais
Ao usar controle de fluxo em Rust, prefira for para iterar sobre coleções, pois é mais seguro e expressivo. Use loop quando precisar de um laço infinito ou quando o número de iterações não for conhecido. Evite usar while true; em vez disso, use loop.
Lembre-se de que if/else como expressão pode tornar o código mais sucinto e imutável, o que é um dos pilares de Rust. Sempre que possível, atribua o resultado de um if a uma variável em vez de usar múltiplas atribuições mutáveis.
Labels devem ser usados com moderação, pois podem tornar o fluxo difícil de seguir. Prefira extrair funções ou reestruturar o código se os loops ficarem muito complexos.
Referências
- Capítulo 3.5 do Livro de Rust: Controle de Fluxo
- Rust by Example: if/else
- Rust by Example: loop
- Rust by Example: while
- Rust by Example: for
- Rust by Example: Nested loops e labels
Exercícios
- Exercício 1: Escreva um programa que determine se um número é par ou ímpar usando
if/elsecomo expressão para atribuir uma string a uma variável. - Exercício 2: Use um loop
whilepara imprimir os números de 10 a 1 em ordem decrescente. - Exercício 3: Crie um programa que some todos os números de 1 a 100 usando um loop
forcom intervalo inclusivo. - Exercício 4: Use um loop
looppara encontrar o primeiro número maior que 1000 que é divisível por 17. Usebreakcom valor para retornar esse número. - Exercício 5: Crie um programa que percorra uma matriz 3x3 (usando loops aninhados) e pare todos os loops quando encontrar o valor 0, usando um label. Imprima a posição (linha, coluna) onde o 0 foi encontrado.
let numero = 7;
let resultado = if numero % 2 == 0 { "par" } else { "ímpar" };
println!("O número {} é {}.", numero, resultado);let mut numero = 10;
while numero >= 1 {
println!("{}", numero);
numero -= 1;
}let mut soma = 0;
for i in 1..=100 {
soma += i;
}
println!("Soma: {}", soma);let mut numero = 1001;
let resultado = loop {
if numero % 17 == 0 {
break numero;
}
numero += 1;
};
println!("O primeiro número é {}", resultado);let matriz = [
[1, 2, 3],
[4, 0, 6],
[7, 8, 9]
];
'encontrado: for (i, linha) in matriz.iter().enumerate() {
for (j, &valor) in linha.iter().enumerate() {
if valor == 0 {
println!("Encontrado 0 na posição ({}, {})", i, j);
break 'encontrado;
}
}
}