Decisões com condicionais
por Callahan WhitmoreEntenda o conceito
Condicionais permitem que o programa escolha o que fazer conforme os dados. Em Rust, o if testa uma condição e executa um bloco entre chaves; else if encadeia novas condições e else cobre o caso restante. Os parênteses em volta da condição são dispensáveis, mas as chaves são obrigatórias, mesmo para uma única linha.
A condição precisa ser sempre um bool. Rust não trata números como verdadeiro ou falso: if estoque { ... } não compila, e você escreve a comparação completa, if estoque > 0. As condições são montadas com os operadores de comparação (==, !=, <, >, <=, >=) e combinadas com && (e), || (ou) e ! (não).
Em Rust, if é uma expressão: ele devolve o valor da última linha do bloco escolhido, o que permite escrever let frete = if subtotal >= 200.0 { 0.0 } else { 18.5 };. Para muitos casos, o match compara um valor com vários padrões (valores exatos, faixas como 1..=7, alternativas com | e guardas com if) e o compilador exige que todos os casos possíveis sejam cobertos.
- A condição do
iftem que serbool: escrevaif qtd > 0, nuncaif qtd. else iftesta as condições em ordem e executa só o primeiro bloco verdadeiro; por isso, comece pela faixa mais restrita.ifdevolve valor:let x = if c { 1 } else { 0 };exigeelsee os dois ramos com o mesmo tipo, sem;na última expressão.&&e||avaliam em curto-circuito: se o lado esquerdo já decide o resultado, o direito nem é calculado.matchaceita valores, faixas (8..=30), alternativas ("sul" | "norte"), tuplas e guardas (x if x > 5), e precisa cobrir todos os casos, geralmente com_no fim.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Boletim com if, else if e operadores lógicos
// if, else if e else para classificar a nota de um aluno.
fn main() {
let aluno = "Marina";
let nota = 6.5;
let faltas = 4;
// A condição precisa ser um bool; não há parênteses obrigatórios.
if nota >= 9.0 {
println!("{aluno}: excelente!");
} else if nota >= 7.0 {
println!("{aluno}: aprovada.");
} else if nota >= 5.0 {
println!("{aluno}: recuperação.");
} else {
println!("{aluno}: reprovada.");
}
// && exige as duas condições; || aceita qualquer uma delas.
if nota >= 5.0 && faltas <= 10 {
println!("Pode fazer a prova de recuperação.");
}
if nota < 5.0 || faltas > 10 {
println!("Precisa refazer a disciplina.");
}
// ! inverte um bool.
let tem_pendencia = false;
if !tem_pendencia {
println!("Nenhuma pendência na secretaria.");
}
}
A cadeia if / else if / else testa as faixas de nota da maior para a menor e executa só o primeiro bloco cuja condição é verdadeira. Com nota 6.5, as duas primeiras falham e a terceira (nota >= 5.0) imprime recuperação; o else final nunca roda nesse caso.
Depois, && exige nota mínima e poucas faltas, || basta uma das condições para obrigar a refazer a disciplina, e ! inverte o bool tem_pendencia. Repare que nenhuma condição usa parênteses e todas são comparações explícitas, porque Rust só aceita bool no if.
02Frete da loja com if como expressão
// if como expressão: o bloco escolhido devolve um valor.
fn main() {
let subtotal = 189.90;
let peso_kg = 3.2;
let cliente_vip = true;
// Os dois ramos devolvem f64; a última expressão não leva ponto e vírgula.
let frete = if subtotal >= 200.0 {
0.0
} else if peso_kg > 5.0 {
35.0
} else {
18.5
};
// A expressão também cabe numa linha só, como um operador ternário.
let desconto = if cliente_vip { subtotal * 0.05 } else { 0.0 };
let total = subtotal - desconto + frete;
println!("Subtotal: R$ {subtotal:.2}");
println!("Desconto VIP: R$ {desconto:.2}");
println!("Frete: R$ {frete:.2}");
println!("Total: R$ {total:.2}");
// O valor pode ser um texto; os dois ramos precisam ter o mesmo tipo.
let mensagem = if frete == 0.0 {
"Frete grátis!"
} else {
"Faltam poucos reais para o frete grátis."
};
println!("{mensagem}");
// Quanto falta para o frete grátis, só quando ele ainda é cobrado.
if frete > 0.0 {
println!("Faltam R$ {:.2}", 200.0 - subtotal);
}
}
Aqui o if não só executa código: ele produz um valor que vai direto para frete, desconto e mensagem. A última expressão de cada bloco, sem ponto e vírgula, é o valor devolvido, e o ; depois da chave final fecha o let.
Como a variável recebe o resultado, todos os ramos precisam devolver o mesmo tipo (f64 no frete, &str na mensagem) e o else é obrigatório: sem ele, o compilador não saberia que valor usar quando a condição fosse falsa. O último if, sem else, volta a ser só um comando que imprime ou não.
03Empréstimos da biblioteca com match
// match compara um valor com vários padrões, na ordem.
fn main() {
let dias_atraso: u32 = 9;
let tipo = "revista";
let renovacoes: u8 = 2;
// Faixas com ..= agrupam valores; o _ cobre o que sobrar.
let situacao = match dias_atraso {
0 => "em dia",
1..=7 => "atraso leve",
8..=30 => "atraso grave",
_ => "bloqueado",
};
println!("Situação: {situacao}");
// match com &str; o _ cobre qualquer outro texto.
let multa_por_dia = match tipo {
"livro" => 1.50,
"revista" | "jornal" => 0.50,
"dvd" => 3.00,
_ => 1.00,
};
println!("Multa: R$ {:.2}", dias_atraso as f64 * multa_por_dia);
// Guarda (if) refina um padrão; tuplas testam dois valores juntos.
let pode_renovar = match (dias_atraso, renovacoes) {
(0, r) if r < 3 => "sim",
(0, _) => "não: limite de renovações atingido",
(d, _) if d <= 7 => "só depois de pagar a multa",
_ => "não: procure o balcão",
};
println!("Pode renovar? {pode_renovar}");
// match precisa cobrir todos os casos; sem o _, não compila.
let aviso = match renovacoes {
0 => "primeira retirada",
1 | 2 => "já renovado",
_ => "renovações esgotadas",
};
println!("Histórico: {aviso}");
}
O match compara um valor com uma lista de padrões, de cima para baixo, e usa o primeiro que casar. O exemplo mostra faixas (1..=7), alternativas com | ("revista" | "jornal") e o curinga _, que cobre qualquer valor não listado. Como todo match devolve um valor, ele também pode ir direto para um let.
No cálculo de renovação, o match testa uma tupla com dois valores ao mesmo tempo: (0, r) if r < 3 casa só quando não há atraso e a guarda é verdadeira. O compilador confere a exaustividade: tirar o último _ de qualquer match gera o erro non-exhaustive patterns.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Ingredientes na despensaFácil
Antes de fazer um bolo, confira se a despensa tem ovos e farinha suficientes. Para cada ingrediente, mostre ok ou quanto falta e, no fim, diga se dá para fazer a receita.
- As quantidades são variáveis
u32: a receita pede 3 ovos e 500 g de farinha; a despensa tem 2 ovos e 800 g. - Cada ingrediente é conferido com um
if/elsepróprio, e a subtração só acontece no ramo em que ela não fica negativa. - A resposta final vem de um
ifusado como expressão, com&&juntando as duas condições. - A saída é idêntica à esperada.
fn main() {
let ovos_receita: u32 = 3;
let ovos_disponiveis: u32 = 2;
let farinha_receita: u32 = 500;
let farinha_disponivel: u32 = 800;
// confira os ovos e a farinha com if / else
// let resposta = if ... { "sim" } else { "não" };
}
Ovos: faltam 1
Farinha: ok (sobram 300 g)
Dá para fazer o bolo? não
Ver dica
Com u32, a conta 2 - 3 não existe: decida primeiro, com a comparação, qual dos dois lados é maior e só então subtraia.
Ver resposta
fn main() {
let ovos_receita: u32 = 3;
let ovos_disponiveis: u32 = 2;
let farinha_receita: u32 = 500;
let farinha_disponivel: u32 = 800;
// Compara o que a receita pede com o que há na despensa
if ovos_disponiveis >= ovos_receita {
println!("Ovos: ok");
} else {
println!("Ovos: faltam {}", ovos_receita - ovos_disponiveis);
}
if farinha_disponivel >= farinha_receita {
println!("Farinha: ok (sobram {} g)", farinha_disponivel - farinha_receita);
} else {
println!("Farinha: faltam {} g", farinha_receita - farinha_disponivel);
}
// && exige os dois ingredientes; o if devolve o texto
let resposta = if ovos_disponiveis >= ovos_receita && farinha_disponivel >= farinha_receita {
"sim"
} else {
"não"
};
println!("Dá para fazer o bolo? {resposta}");
}
Passo a passo
- As quatro variáveis são
u32porque quantidades de ingredientes nunca são negativas; isso obriga a tomar cuidado com subtrações. if ovos_disponiveis >= ovos_receitacompara os dois valores e gera umbool, o único tipo que oifaceita como condição.- No
elsedos ovos, a contaovos_receita - ovos_disponiveisé segura: só chegamos ali quando a receita pede mais do que há, então o resultado é positivo (1). - A farinha segue a mesma ideia, mas agora o ramo verdadeiro também calcula a sobra (
800 - 500), mostrada entre parênteses. let resposta = if ... { "sim" } else { "não" };usa oifcomo expressão: o bloco escolhido devolve um&str, guardado na variável.- O
&&só dátruequando os dois ingredientes bastam; como faltam ovos, a resposta énão.
Por que funciona
Cada if / else escolhe exatamente um de dois caminhos conforme a comparação. Colocar a subtração dentro do ramo certo garante que ela nunca fique negativa, o que importa com u32. No fim, && combina as duas condições num único bool, e o if como expressão transforma esse bool no texto da resposta, sem precisar de uma variável mut.
Erros comuns
- Usar
=no lugar de>=ou==:if ovos_disponiveis = ovos_receitanão compila (mismatched types, esperadobool).=é atribuição; para comparar, use os operadores de comparação. - Condição numérica:
if ovos_disponiveis { ... }também falha comexpected bool, found u32. Rust não converte números em verdadeiro ou falso; escreva a comparação completa. - Subtrair fora do ramo certo:
println!("{}", ovos_disponiveis - ovos_receita);com 2 e 3 gerathis arithmetic operation will overflow, porqueu32não guarda negativos. Subtraia o menor do maior, dentro do ramo que garante isso. - Ponto e vírgula nos ramos:
if ... { "sim"; } else { "não"; }faz cada bloco devolver()em vez do texto. Deixe a última expressão sem;.
Outra forma de resolver
Dá para guardar as condições em variáveis antes: let tem_ovos = ovos_disponiveis >= ovos_receita; e depois if tem_ovos && tem_farinha. Isso é preferível quando a mesma condição é usada mais de uma vez, como aqui, porque evita repetir a comparação e dá nome à regra.
Saída esperada
Ovos: faltam 1
Farinha: ok (sobram 300 g)
Dá para fazer o bolo? não
02Medalha no fim da faseMédio
Ao terminar uma fase, o jogador recebe 200 pontos de bônus se levou menos de 120 segundos e mais 500 se, além disso, não perdeu nenhuma vida. Calcule o total e escolha a medalha: ouro a partir de 1000, prata a partir de 800, bronze a partir de 500 e nenhuma abaixo disso.
- Os valores da fase são 870 pontos, 95 segundos e 1 vida perdida, em variáveis
u32. - Cada bônus é calculado com um
ifusado como expressão, e o bônus sem dano usa&&. - A medalha é uma cadeia
if/else if/elseque devolve um&str, testando a partir da faixa mais alta. - A medalha é calculada sobre o total com os bônus, e a saída é idêntica à esperada.
fn main() {
let pontos: u32 = 870;
let tempo_s: u32 = 95;
let vidas_perdidas: u32 = 1;
// bônus de tempo e bônus sem dano com if como expressão
// medalha com if / else if / else
}
Pontos da fase: 870
Bônus de tempo: 200
Bônus sem dano: 0
Total: 1070
Medalha: ouro
Ver dica
Num else if, só o primeiro ramo verdadeiro conta: pense em qual faixa precisa ser testada antes para que 1070 não seja classificado numa medalha menor.
Ver resposta
fn main() {
let pontos: u32 = 870;
let tempo_s: u32 = 95;
let vidas_perdidas: u32 = 1;
// if como expressão: cada ramo devolve um u32
let bonus_tempo = if tempo_s < 120 { 200 } else { 0 };
let bonus_sem_dano = if vidas_perdidas == 0 && tempo_s < 120 { 500 } else { 0 };
let total = pontos + bonus_tempo + bonus_sem_dano;
// Cadeia else if, da faixa mais alta para a mais baixa
let medalha = if total >= 1000 {
"ouro"
} else if total >= 800 {
"prata"
} else if total >= 500 {
"bronze"
} else {
"nenhuma"
};
println!("Pontos da fase: {pontos}");
println!("Bônus de tempo: {bonus_tempo}");
println!("Bônus sem dano: {bonus_sem_dano}");
println!("Total: {total}");
println!("Medalha: {medalha}");
}
Passo a passo
let bonus_tempo = if tempo_s < 120 { 200 } else { 0 };escolhe o valor em uma linha; os dois ramos são inteiros, então o tipo bate com ou32do resto da conta.- O bônus sem dano exige duas coisas ao mesmo tempo, por isso usa
&&:vidas_perdidas == 0é falso, então o resultado é 0 mesmo com o tempo bom. totalsoma os pontos e os dois bônus: 870 + 200 + 0 = 1070.- A medalha é uma cadeia
if/else if/elsecujo resultado vai direto paralet medalha; cada ramo devolve um&str. - As faixas começam pela mais alta (
>= 1000): como o primeiro ramo verdadeiro encerra a cadeia, 1070 fica com ouro e os testes seguintes nem são avaliados. - O
elsefinal garante um valor em todos os casos, o que o compilador exige quando oifalimenta uma variável.
Por que funciona
Como if é uma expressão em Rust, cada decisão vira um valor imutável, calculado uma vez, sem let mut e sem atribuições espalhadas. A cadeia else if funciona como uma escada: o programa desce até o primeiro degrau verdadeiro, por isso a ordem das faixas define o resultado. Note que, sem o bônus de tempo, os 870 pontos dariam só prata.
Erros comuns
- Faixas na ordem errada: começar com
if total >= 500 { "bronze" }faz qualquer total acima de 500 virar bronze, porque o primeiro ramo verdadeiro já encerra a cadeia. Teste da faixa mais alta para a mais baixa. - Esquecer o
else:let bonus_tempo = if tempo_s < 120 { 200 };não compila (if may be missing an else clause). Quando oifdevolve valor, todos os caminhos precisam devolver algo. - Tipos diferentes nos ramos:
if tempo_s < 120 { 200 } else { "zero" }geraif and else have incompatible types. Os dois ramos precisam ter o mesmo tipo. - Medalha antes dos bônus: usar
pontosem vez detotalna cadeia dáprata, e a saída deixa de bater.
Outra forma de resolver
A medalha também pode sair de um match com faixas: match total { 1000.. => "ouro", 800..=999 => "prata", 500..=799 => "bronze", _ => "nenhuma" }. Ele é preferível quando as faixas são muitas e fixas, porque cada uma fica explícita e não depende da ordem dos testes; a cadeia else if é melhor quando as condições envolvem várias variáveis.
Saída esperada
Pontos da fase: 870
Bônus de tempo: 200
Bônus sem dano: 0
Total: 1070
Medalha: ouro
03Frete e cupom do pedido com matchDesafio
Feche um pedido da loja virtual em centavos: calcule o frete conforme a região e o peso, aplique o cupom (que tem regras próprias) e mostre o total e o prazo de entrega, usando match em todas as decisões.
- O frete vem de um
matchsobre a tupla(regiao, peso_g), com faixas de peso (0..=1000,1001..=5000) e regiões agrupadas com|: sudeste até 1 kg 1290, acima 1990; sul ou centro-oeste até 1 kg 1690, até 5 kg 2490; outras regiões até 5 kg 3290; o resto 4990. - O cupom é um
matchcom guardas que devolve a tupla(desconto, aviso):DEZdá 10% com subtotal a partir de R$ 100,00;FRETEzera o frete com subtotal a partir de R$ 50,00; texto vazio ésem cupome qualquer outro écupom inválido. - O prazo é um
matchsobre a região: sudeste 2 dias úteis, sul ou centro-oeste 4, as demais 7. - Com região
sul, 2300 g, subtotal 15990 e cupomDEZ, a saída é idêntica à esperada, com os centavos em dois dígitos.
fn main() {
let regiao = "sul";
let peso_g: u32 = 2300;
let subtotal: u32 = 15990; // centavos
let cupom = "DEZ";
// let frete: u32 = match (regiao, peso_g) { ... };
// let (desconto, aviso): (u32, &str) = match cupom { ... };
// total, impressão e prazo de entrega
}
Frete: R$ 24,90
Desconto: R$ 15,99 (cupom DEZ aplicado)
Total: R$ 168,81
Entrega em 4 dias úteis
Ver dica
Num match, a guarda (if) vem logo depois do padrão; quando ela falha, o próximo braço é testado. Pense em qual braço do DEZ precisa vir primeiro.
Ver resposta
fn main() {
let regiao = "sul";
let peso_g: u32 = 2300;
let subtotal: u32 = 15990; // centavos
let cupom = "DEZ";
// Frete: tupla (região, peso) com faixas e |
let frete: u32 = match (regiao, peso_g) {
("sudeste", 0..=1000) => 1290,
("sudeste", _) => 1990,
("sul" | "centro-oeste", 0..=1000) => 1690,
("sul" | "centro-oeste", 1001..=5000) => 2490,
(_, 0..=5000) => 3290,
_ => 4990,
};
// Cupom: guardas conferem o valor mínimo de cada um
let (desconto, aviso): (u32, &str) = match cupom {
"DEZ" if subtotal >= 10000 => (subtotal / 10, "cupom DEZ aplicado"),
"DEZ" => (0, "cupom DEZ exige R$ 100,00"),
"FRETE" if subtotal >= 5000 => (frete, "frete por conta da loja"),
"" => (0, "sem cupom"),
_ => (0, "cupom inválido"),
};
let total = subtotal + frete - desconto;
println!("Frete: R$ {},{:02}", frete / 100, frete % 100);
println!("Desconto: R$ {},{:02} ({aviso})", desconto / 100, desconto % 100);
println!("Total: R$ {},{:02}", total / 100, total % 100);
// Prazo: match sobre a região, com o _ cobrindo o resto
let prazo = match regiao {
"sudeste" => "2 dias úteis",
"sul" | "centro-oeste" => "4 dias úteis",
_ => "7 dias úteis",
};
println!("Entrega em {prazo}");
}
Passo a passo
- Os valores de dinheiro ficam em centavos (
u32), como na lição anterior, para que as contas sejam exatas e a divisão do desconto seja inteira. match (regiao, peso_g)testa os dois valores juntos. Cada braço combina um texto com uma faixa:("sul" | "centro-oeste", 1001..=5000)casa com("sul", 2300)e devolve 2490.- Os braços com
_aparecem por último:("sudeste", _)aceita qualquer peso no sudeste,(_, 0..=5000)qualquer região leve e o_final cobre o que sobrou, garantindo a exaustividade. - No cupom,
"DEZ" if subtotal >= 10000usa uma guarda; quando ela é verdadeira, o desconto ésubtotal / 10(1599 centavos). Se fosse falsa, o braço seguinte,"DEZ"sem guarda, explicaria o motivo. - O
matchdo cupom devolve uma tupla, desmontada no própriolet (desconto, aviso); todos os braços devolvem(u32, &str), por isso o tipo é anotado uma vez só. totalsoma subtotal e frete e subtrai o desconto; os valores são impressos com/ 100e% 100, e{:02}mantém dois dígitos nos centavos.- O prazo usa um
matchsimples sobre o texto da região, com|para agrupar sul e centro-oeste e_para as outras.
Por que funciona
O match percorre os braços de cima para baixo e executa o primeiro cujo padrão casa e cuja guarda (se houver) é verdadeira. Por isso, a ordem importa: os casos mais específicos vêm antes dos genéricos com _. Como o compilador exige que todos os valores possíveis sejam cobertos, é impossível esquecer uma região ou um cupom desconhecido: sem o _ final, o código nem compila. E, como cada match é uma expressão, frete, desconto e prazo saem imutáveis e prontos para usar.
Erros comuns
- Esquecer o curinga: sem o
_nomatchdo prazo, o compilador recusa comnon-exhaustive patterns: &_ not covered, porque um&strpode ter infinitos valores. Termine com_ =>. - Braço genérico antes do específico: com
"DEZ" => (0, ...)antes de"DEZ" if subtotal >= 10000, o segundo nunca é alcançado; o compilador avisaunreachable patterne o desconto sai zero. Coloque o braço com guarda primeiro. - Faixas que se sobrepõem por engano: escrever
0..=1000e depois1000..=5000compila, mas um pacote de exatamente 1000 g sempre cai na primeira faixa. Use limites contínuos sem repetir valores, como1001..=5000. - Tipos diferentes nos braços: devolver
(subtotal / 10, "ok")num braço e só0em outro geramatch arms have incompatible types. Todos os braços devolvem a mesma tupla(u32, &str).
Outra forma de resolver
O frete poderia ser decidido com dois níveis de if: primeiro a região, com if regiao == "sudeste" { ... } else if regiao == "sul" || regiao == "centro-oeste" { ... }, e dentro de cada ramo outro if para o peso. Funciona, mas os casos ficam espalhados e o compilador não confere se algum ficou de fora; o match sobre a tupla é preferível quando a decisão depende de duas variáveis com combinações fixas, como uma tabela de frete.
Saída esperada
Frete: R$ 24,90
Desconto: R$ 15,99 (cupom DEZ aplicado)
Total: R$ 168,81
Entrega em 4 dias úteis