No artigo Funções, Expressões e Como Rust Pensa Diferente sobre Retorno de Valores, 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.
Exercícios
Exercício 1
Reescreva o trecho abaixo usando if como expression, sem mut.
let mut taxa = 0.0;
if valor > 1000.0 {
taxa = 0.02;
} else {
taxa = 0.05;
}
Ver resposta
✓ Resposta:
let taxa = if valor > 1000.0 { 0.02 } else { 0.05 };
Além de mais curto, o resultado é imutável: o valor é decidido de uma vez e não pode ser alterado depois por engano. É o padrão idiomático em Rust — o mut ali existia só porque a linguagem de origem não tinha if como expression.
Exercício 2
Explique a diferença entre loop, while e for, e diga qual deles é o único capaz de devolver um valor.
Ver resposta
✓ Resposta: loop repete indefinidamente até um break; while testa uma condição antes de cada repetição; for percorre um iterador, do primeiro ao último elemento. Apenas o loop devolve valor, através do break:
let primeiro_multiplo = loop {
contador += 1;
if contador % 7 == 0 {
break contador;
}
};
O for é o mais usado porque elimina a aritmética de índice, que é onde moram os erros de um a mais ou um a menos.
Exercício 3
O laço abaixo tem um bug. Identifique-o e corrija usando for.
let nomes = ["Ana", "Bruno", "Carla"];
let mut i = 0;
while i <= nomes.len() {
println!("{}", nomes[i]);
i += 1;
}
Ver resposta
✓ Resposta: A condição usa <= em vez de <, então o laço tenta acessar nomes[3] num array de três elementos. Rust não lê memória inválida: verifica o limite em tempo de execução e encerra o programa com index out of bounds: the len is 3 but the index is 3 — um panic, não uma falha silenciosa como seria em C. A correção idiomática elimina o índice:
for nome in nomes {
println!("{nome}");
}
Assim o erro deixa de ser possível: não há condição de parada para escrever errado.
Exercício 4
Escreva um laço que percorra os números de 1 a 20 e pare no primeiro divisível por 13, guardando-o numa variável. Explique a escolha da construção.
Ver resposta
✓ Resposta:
fn main() {
let mut achado = None;
for n in 1..=20 {
if n % 13 == 0 {
achado = Some(n);
break;
}
}
println!("{achado:?}"); // Some(13)
}
O for com o intervalo inclusivo 1..=20 evita escrever a condição de parada à mão. O Option cobre o caso de não haver nenhum divisível no intervalo, sem inventar um valor mágico como -1 para significar ausência.
Exercício 5
Explique o que fazem os rótulos em laços aninhados e por que eles são preferíveis a uma variável de controle.
Ver resposta
✓ Resposta: Um rótulo nomeia o laço, permitindo que break e continue escolham a qual deles se aplicam — sem ele, ambos afetam apenas o laço mais interno.
'externo: for i in 0..5 {
for j in 0..5 {
if i * j > 6 {
break 'externo;
}
}
}
A alternativa sem rótulo é uma variável booleana testada nos dois laços, que espalha a lógica de saída por vários pontos e é fácil de esquecer em um deles. O rótulo diz exatamente onde o fluxo vai parar, no ponto em que a decisão é tomada.
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
- Rust by Example — for e iteradores — por que
for x in colecaoconsome o valor e o que muda com&ou&mut— https://doc.rust-lang.org/rust-by-example/flow_control/for.html std::iter::Iterator—enumerate,ziperev, que dispensam o laço por índice na maior parte dos casos — https://doc.rust-lang.org/std/iter/trait.Iterator.html