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
- The Rust Programming Language, Cap. 3.5 — Control Flow — https://doc.rust-lang.org/book/ch03-05-control-flow.html
- Rust by Example — Flow of Control — https://doc.rust-lang.org/rust-by-example/flow_control.html
- Rust Reference — Loops — https://doc.rust-lang.org/reference/expressions/loop-expr.html
- Rust Reference — If expressions — https://doc.rust-lang.org/reference/expressions/if-expr.html
- Rustlings, seção
if— https://github.com/rust-lang/rustlings