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 = 10

També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, 4

O 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 = 20

Isso é ú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 testar

Na 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 = 2

Os 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

Exercícios

  1. Exercício 1: Escreva um programa que determine se um número é par ou ímpar usando if/else como expressão para atribuir uma string a uma variável.
  2. ✓ Resposta:
    let numero = 7;
    let resultado = if numero % 2 == 0 { "par" } else { "ímpar" };
    println!("O número {} é {}.", numero, resultado);
  3. Exercício 2: Use um loop while para imprimir os números de 10 a 1 em ordem decrescente.
  4. ✓ Resposta:
    let mut numero = 10;
    while numero >= 1 {
        println!("{}", numero);
        numero -= 1;
    }
  5. Exercício 3: Crie um programa que some todos os números de 1 a 100 usando um loop for com intervalo inclusivo.
  6. ✓ Resposta:
    let mut soma = 0;
    for i in 1..=100 {
        soma += i;
    }
    println!("Soma: {}", soma);
  7. Exercício 4: Use um loop loop para encontrar o primeiro número maior que 1000 que é divisível por 17. Use break com valor para retornar esse número.
  8. ✓ Resposta:
    let mut numero = 1001;
    let resultado = loop {
        if numero % 17 == 0 {
            break numero;
        }
        numero += 1;
    };
    println!("O primeiro número é {}", resultado);
  9. 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.
  10. ✓ Resposta:
    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;
            }
        }
    }