Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Decisões com condicionais

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.

Rust Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
Rust / Fundamentos

Decisões com condicionais

por Callahan Whitmore
Iniciante

Entenda 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.

↳ Pontos-chave
  • A condição do if tem que ser bool: escreva if qtd > 0, nunca if qtd.
  • else if testa as condições em ordem e executa só o primeiro bloco verdadeiro; por isso, comece pela faixa mais restrita.
  • if devolve valor: let x = if c { 1 } else { 0 }; exige else e 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.
  • match aceita 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

boletim.rs
// 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

frete.rs
// 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

emprestimo.rs
// 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.

↳ Requisitos
  • 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 / else próprio, e a subtração só acontece no ramo em que ela não fica negativa.
  • A resposta final vem de um if usado como expressão, com && juntando as duas condições.
  • A saída é idêntica à esperada.
código inicial
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" };
}
saída esperada
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

  1. As quatro variáveis são u32 porque quantidades de ingredientes nunca são negativas; isso obriga a tomar cuidado com subtrações.
  2. if ovos_disponiveis >= ovos_receita compara os dois valores e gera um bool, o único tipo que o if aceita como condição.
  3. No else dos ovos, a conta ovos_receita - ovos_disponiveis é segura: só chegamos ali quando a receita pede mais do que há, então o resultado é positivo (1).
  4. A farinha segue a mesma ideia, mas agora o ramo verdadeiro também calcula a sobra (800 - 500), mostrada entre parênteses.
  5. let resposta = if ... { "sim" } else { "não" }; usa o if como expressão: o bloco escolhido devolve um &str, guardado na variável.
  6. O && só dá true quando 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_receita não compila (mismatched types, esperado bool). = é atribuição; para comparar, use os operadores de comparação.
  • Condição numérica: if ovos_disponiveis { ... } também falha com expected 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 gera this arithmetic operation will overflow, porque u32 nã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.

↳ Requisitos
  • Os valores da fase são 870 pontos, 95 segundos e 1 vida perdida, em variáveis u32.
  • Cada bônus é calculado com um if usado como expressão, e o bônus sem dano usa &&.
  • A medalha é uma cadeia if / else if / else que 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.
código inicial
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
}
saída esperada
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

  1. 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 o u32 do resto da conta.
  2. 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.
  3. total soma os pontos e os dois bônus: 870 + 200 + 0 = 1070.
  4. A medalha é uma cadeia if / else if / else cujo resultado vai direto para let medalha; cada ramo devolve um &str.
  5. 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.
  6. O else final garante um valor em todos os casos, o que o compilador exige quando o if alimenta 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 o if devolve valor, todos os caminhos precisam devolver algo.
  • Tipos diferentes nos ramos: if tempo_s < 120 { 200 } else { "zero" } gera if and else have incompatible types. Os dois ramos precisam ter o mesmo tipo.
  • Medalha antes dos bônus: usar pontos em vez de total na 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.

↳ Requisitos
  • O frete vem de um match sobre 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 match com guardas que devolve a tupla (desconto, aviso): DEZ dá 10% com subtotal a partir de R$ 100,00; FRETE zera o frete com subtotal a partir de R$ 50,00; texto vazio é sem cupom e qualquer outro é cupom inválido.
  • O prazo é um match sobre a região: sudeste 2 dias úteis, sul ou centro-oeste 4, as demais 7.
  • Com região sul, 2300 g, subtotal 15990 e cupom DEZ, a saída é idêntica à esperada, com os centavos em dois dígitos.
código inicial
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
}
saída esperada
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

  1. 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.
  2. 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.
  3. 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.
  4. No cupom, "DEZ" if subtotal >= 10000 usa uma guarda; quando ela é verdadeira, o desconto é subtotal / 10 (1599 centavos). Se fosse falsa, o braço seguinte, "DEZ" sem guarda, explicaria o motivo.
  5. O match do cupom devolve uma tupla, desmontada no próprio let (desconto, aviso); todos os braços devolvem (u32, &str), por isso o tipo é anotado uma vez só.
  6. total soma subtotal e frete e subtrai o desconto; os valores são impressos com / 100 e % 100, e {:02} mantém dois dígitos nos centavos.
  7. O prazo usa um match simples 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 _ no match do prazo, o compilador recusa com non-exhaustive patterns: &_ not covered, porque um &str pode 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 avisa unreachable pattern e o desconto sai zero. Coloque o braço com guarda primeiro.
  • Faixas que se sobrepõem por engano: escrever 0..=1000 e depois 1000..=5000 compila, mas um pacote de exatamente 1000 g sempre cai na primeira faixa. Use limites contínuos sem repetir valores, como 1001..=5000.
  • Tipos diferentes nos braços: devolver (subtotal / 10, "ok") num braço e só 0 em outro gera match 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

Últimas lições

Ver o arquivo completo →