Controle de Fluxo — if, loop, while e for como você nunca viu antes

No artigo anterior, aprendemos que Rust distingue statements de expressions, e que a ausência de ponto e vírgula no final de um bloco significa retorno de valor. Hoje esse conceito vai se expandir de forma elegante — porque em Rust, as próprias estruturas de controle de fluxo são expressions. Isso muda tudo.


O if que você conhece — e o que você não conhece

A forma básica do if em Rust é familiar:

fn main() {
    let temperatura = 38.5;

    if temperatura > 37.5 {
        println!("Febre detectada.");
    } else if temperatura > 36.0 {
        println!("Temperatura normal.");
    } else {
        println!("Abaixo do normal.");
    }
}

Até aqui, nada surpreendente. Mas há dois detalhes importantes que separam o if de Rust do de outras linguagens:

Primeiro: não há parênteses em torno da condição. Em C, Java ou JavaScript você escreve if (x > 0). Em Rust, os parênteses são desnecessários e o compilador inclusive avisa se você os usar sem necessidade.

Segundo: a condição deve ser um booleano. Sem exceções. Em C você pode escrever if (1) ou if (ponteiro). Em Rust, isso é um erro de compilação. A condição tem que ser explicitamente true ou false. Isso elimina uma classe inteira de bugs sutis.


if como expression — a grande virada

Aqui está o que torna o if de Rust especial. Como ele é uma expression, pode produzir um valor:

fn main() {
    let aprovado = true;

    let mensagem = if aprovado {
        "Parabéns, você passou!"
    } else {
        "Tente novamente."
    };

    println!("{mensagem}");
}

O if inteiro avalia para um valor, que é atribuído a mensagem. Isso substitui o operador ternário ? : de outras linguagens — de forma mais legível e mais poderosa.

Mas há uma regra importante: ambos os ramos devem retornar o mesmo tipo. O compilador não aceita isso:

fn main() {
    let x = 5;
    let y = if x > 0 { 42 } else { "negativo" }; // ERRO de tipo!
}

Um ramo retorna i32, o outro retorna &str. Rust recusa. Os tipos precisam ser compatíveis — e isso é verificado em tempo de compilação, não em execução.


loop — repetição incondicional

O loop em Rust é o laço mais simples: repete para sempre até que você peça para parar com break:

fn main() {
    let mut tentativas = 0;

    loop {
        tentativas += 1;
        println!("Tentativa {tentativas}");

        if tentativas == 3 {
            println!("Limite atingido.");
            break;
        }
    }
}

Saída:

Tentativa 1
Tentativa 2
Tentativa 3
Limite atingido.

Mas o loop também é uma expression. Você pode retornar um valor do break:

fn main() {
    let mut contador = 0;

    let resultado = loop {
        contador += 1;
        if contador == 10 {
            break contador * 2; // retorna 20
        }
    };

    println!("Resultado: {resultado}"); // 20
}

Isso é especialmente útil quando você precisa tentar uma operação repetidamente até obter um resultado válido — como ler entrada do usuário ou aguardar uma conexão.


Labels em loops aninhados

Quando você tem loops aninhados, o break por padrão afeta apenas o loop mais interno. Para sair de um loop externo, Rust oferece labels:

fn main() {
    let mut encontrado = false;

    'externo: for i in 0..5 {
        for j in 0..5 {
            if i + j == 6 {
                println!("Encontrado: i={i}, j={j}");
                encontrado = true;
                break 'externo; // sai do loop externo
            }
        }
    }

    if encontrado {
        println!("Busca concluída.");
    }
}

Labels começam com apóstrofo (') — uma sintaxe incomum, mas fácil de identificar visualmente. O continue também aceita labels para pular para a próxima iteração de um loop específico.


while — repetição condicional

O while é direto ao ponto: repete enquanto a condição for verdadeira:

fn main() {
    let mut n = 1;

    while n < 100 {
        n *= 2;
    }

    println!("Primeira potência de 2 acima de 100: {n}");
}

Saída:

Primeira potência de 2 acima de 100: 128

Um padrão muito comum em Rust é o while let, que combina o laço com pattern matching — veremos mais sobre isso nos artigos de enums. Por enquanto, um vislumbre:

fn main() {
    let mut pilha = vec![1, 2, 3, 4, 5];

    while let Some(topo) = pilha.pop() {
        println!("Removido: {topo}");
    }

    println!("Pilha vazia.");
}

Saída:

Removido: 5
Removido: 4
Removido: 3
Removido: 2
Removido: 1
Pilha vazia.

O loop continua enquanto pilha.pop() retornar Some(valor), e para automaticamente quando retornar None — ou seja, quando a pilha esvaziar. Elegante e seguro.


for — o laço mais usado em Rust

O for em Rust não opera sobre índices como em C. Ele itera sobre iteradores — e isso o torna ao mesmo tempo mais seguro e mais expressivo:

fn main() {
    let frutas = ["maçã", "banana", "laranja", "uva"];

    for fruta in frutas {
        println!("Fruta: {fruta}");
    }
}

Não há risco de acessar um índice inválido. Não há off-by-one errors. O for consome cada elemento do iterador na ordem correta e para sozinho.

Ranges

Para iterar sobre números, Rust usa ranges:

fn main() {
    // 1 até 5, incluindo o 5
    for i in 1..=5 {
        println!("{i}");
    }

    // 0 até 4, excluindo o 5
    for i in 0..5 {
        println!("{i}");
    }
}

1..=5 é um range inclusivo — inclui o valor final. 0..5 é um range exclusivo — vai de 0 a 4. Essa distinção é explícita na sintaxe, o que elimina ambiguidade.

Iterando com índice

Quando você precisa do índice junto com o valor, use .enumerate():

fn main() {
    let linguagens = ["Rust", "Go", "C", "Python"];

    for (indice, linguagem) in linguagens.iter().enumerate() {
        println!("{}. {}", indice + 1, linguagem);
    }
}

Saída:

1. Rust
2. Go
3. C
4. Python

Um programa completo: tabuada

Vamos unir tudo que aprendemos em um programa que gera tabuadas:

fn tabuada(numero: u32) {
    println!("\n── Tabuada do {} ──", numero);

    for i in 1..=10 {
        let resultado = numero * i;
        let linha = if i == 10 {
            format!("{numero} × {i:2} = {resultado}")
        } else {
            format!("{numero} × {i:2} = {resultado}")
        };
        println!("{linha}");
    }
}

fn main() {
    for n in 1..=5 {
        tabuada(n);
    }
}

Saída (primeiras linhas):

── Tabuada do 1 ──
1 × 1 = 1
1 × 2 = 2
...
── Tabuada do 2 ──
2 × 1 = 2
2 × 2 = 4
...

O que você deve guardar deste artigo

Três ideias centrais:

Primeira: if, loop e blocos são expressions em Rust. Eles produzem valores. Isso elimina operadores ternários, variáveis temporárias desnecessárias e torna o código mais direto.

Segunda: tipos devem ser consistentes. Se um if retorna valor, todos os ramos retornam o mesmo tipo. O compilador garante isso antes de qualquer execução.

Terceira: o for em Rust não é um laço de índices — é um laço de iteradores. Isso é fundamental para entender Rust, pois iteradores permeiam toda a linguagem e serão um dos temas mais ricos da série.


Fontes e leituras recomendadas