Em Rust, o match é uma ferramenta poderosa para controle de fluxo baseado em padrões. No entanto, em situações onde você só se importa com um único padrão, o match pode parecer verboso. Para esses casos, Rust oferece construções mais concisas: if let e while let. Elas são açúcares sintáticos que simplificam o código, mantendo a segurança e expressividade do sistema de padrões.

Açúcar para match

if let é uma forma compacta de usar match quando você quer executar código apenas se um valor corresponder a um determinado padrão, ignorando os outros casos. Por exemplo, ao invés de escrever:

let optional = Some(5);
match optional {
    Some(x) => println!("O valor é {}", x),
    _ => (),
}

Você pode usar if let:

if let Some(x) = optional {
    println!("O valor é {}", x);
}

Da mesma forma, while let repete um loop enquanto um padrão corresponde a um valor. É útil para iterar sobre iteradores que retornam Option ou Result. Exemplo:

let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
    println!("{}", top);
}

Isso substitui um match dentro de um loop loop com verificação de padrão e break.

Quando usar

Use if let quando você só se importa com um caso de um enum ou um valor que implementa Into para um padrão. É ideal para desestruturar Option, Result ou enums personalizados onde apenas uma variante é relevante. Evite usar if let quando você precisa lidar com múltiplos casos — nesse caso, match é mais adequado.

while let é útil para loops que devem continuar enquanto uma condição de padrão for verdadeira. É comum em iteradores que retornam Option (como next()) ou em situações onde você está consumindo uma estrutura de dados até que ela se esgote. Por exemplo, ao ler linhas de um arquivo com BufReader, que retorna Option<Result<String>>, você pode usar while let para processar cada linha.

Combinando com else

if let pode ser combinado com else para executar código quando o padrão não corresponde. Isso é equivalente a um match com um braço _. Exemplo:

let number = Some(7);
if let Some(x) = number {
    println!("Número: {}", x);
} else {
    println!("Nenhum número");
}

Você também pode encadear if let com else if let para verificar múltiplos padrões, embora isso possa se tornar menos legível que um match.

Para while let, não há uma cláusula else direta, mas você pode usar um if dentro do loop para verificar se o padrão falhou, ou usar break.

Exemplos

Exemplo 1: Option com if let

fn main() {
    let x: Option = Some(10);
    if let Some(val) = x {
        println!("Valor: {}", val);
    } else {
        println!("Nenhum valor");
    }
}

Exemplo 2: Result com if let

fn main() {
    let result: Result = Ok(42);
    if let Ok(num) = result {
        println!("Sucesso: {}", num);
    } else {
        println!("Erro");
    }
}

Exemplo 3: Enum personalizado

enum Mensagem {
    Texto(String),
    Sair,
}

fn main() {
    let msg = Mensagem::Texto(String::from("Olá"));
    if let Mensagem::Texto(t) = msg {
        println!("Mensagem: {}", t);
    }
}

Exemplo 4: while let com iterador

fn main() {
    let valores = vec![1, 2, 3, 4, 5];
    let mut iter = valores.iter();
    while let Some(num) = iter.next() {
        println!("Número: {}", num);
    }
}

Exemplo 5: while let com Option retornado por função

fn proximo_par(limite: i32) -> Option {
    if limite < 0 { None } else { Some(limite + 2) }
}

fn main() {
    let mut atual = 0;
    while let Some(par) = proximo_par(atual) {
        println!("Par: {}", par);
        atual = par;
        if atual > 10 { break; }
    }
}

Boas práticas

  • Prefira if let quando você só precisa de um caso, pois é mais conciso e claro.
  • Evite encadear muitos else if let; nesse caso, match é mais legível.
  • Use while let para loops que consomem iteradores ou padrões que mudam.
  • Lembre-se de que if let e while let funcionam com qualquer padrão, não apenas com enums.

Referências

Exercícios

  1. Escreva uma função que recebe um Option<i32> e imprime o valor se for Some, caso contrário imprime "Nada". Use if let.

    ✓ Resposta:
    fn imprimir_opcional(x: Option) {
        if let Some(val) = x {
            println!("{}", val);
        } else {
            println!("Nada");
        }
    }
  2. Use while let para consumir um iterador de números até que ele retorne None, imprimindo cada número.

    ✓ Resposta:
    fn main() {
        let numeros = vec![1, 2, 3];
        let mut iter = numeros.iter();
        while let Some(num) = iter.next() {
            println!("{}", num);
        }
    }
  3. Crie um enum Cor com variantes RGB(u8, u8, u8) e Hex(String). Use if let para extrair o valor RGB e imprimir.

    ✓ Resposta:
    enum Cor {
        RGB(u8, u8, u8),
        Hex(String),
    }
    
    fn main() {
        let cor = Cor::RGB(255, 0, 0);
        if let Cor::RGB(r, g, b) = cor {
            println!("RGB: ({}, {}, {})", r, g, b);
        }
    }
  4. Escreva um loop que use while let para ler linhas de uma string (simulando um buffer) até encontrar uma linha vazia. Use lines().

    ✓ Resposta:
    fn main() {
        let texto = "linha1\nlinha2\n\nlinha3";
        let mut linhas = texto.lines();
        while let Some(linha) = linhas.next() {
            if linha.is_empty() {
                break;
            }
            println!("{}", linha);
        }
    }
  5. Dado um Result<i32, String>, use if let com else para imprimir o valor se for Ok ou a mensagem de erro se for Err.

    ✓ Resposta:
    fn main() {
        let resultado: Result = Ok(10);
        if let Ok(val) = resultado {
            println!("Valor: {}", val);
        } else if let Err(err) = resultado {
            println!("Erro: {}", err);
        }
    }