Panic em unwrap(): Como Evitar no Rust

Como evitar panics causados por unwrap() em Option e Result no Rust. Aprenda alternativas seguras como match, if let, unwrap_or, map e o operador ? para tratamento de erros.

Panic em unwrap(): Como Evitar

O panic por unwrap() é provavelmente o erro de runtime mais comum no Rust. Ele ocorre quando você chama .unwrap() em um Option::None ou Result::Err. Diferente dos erros de compilação, esse problema só aparece durante a execução do programa.

A Mensagem de Erro

thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', src/main.rs:5:30
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Para Result:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:3:47

O Que Significa

Option<T> e Result<T, E> são os tipos do Rust para representar valores que podem estar ausentes ou operações que podem falhar:

  • Option<T> pode ser Some(valor) ou None
  • Result<T, E> pode ser Ok(valor) ou Err(erro)

O método .unwrap() extrai o valor interno, mas entra em panic se não houver valor. É como dizer “tenho certeza absoluta que há um valor aqui” — e se não houver, o programa aborta.

Usar unwrap() em produção é geralmente um erro. É aceitável em protótipos, testes e situações onde você tem certeza lógica de que o valor existe, mas mesmo assim há alternativas melhores.

Código com Erro

fn main() {
    let numeros = vec![1, 2, 3];

    // PANIC: índice 10 não existe, get() retorna None
    let valor = numeros.get(10).unwrap();

    // PANIC: arquivo pode não existir
    let conteudo = std::fs::read_to_string("inexistente.txt").unwrap();

    // PANIC: parse pode falhar
    let numero: i32 = "abc".parse().unwrap();
}

Como Resolver

Solução 1: Usar match para Tratamento Explícito

O tratamento mais completo e explícito:

fn main() {
    let numeros = vec![1, 2, 3];

    match numeros.get(10) {
        Some(valor) => println!("Valor: {}", valor),
        None => println!("Índice fora dos limites"),
    }

    match std::fs::read_to_string("config.txt") {
        Ok(conteudo) => println!("Conteúdo: {}", conteudo),
        Err(erro) => eprintln!("Erro ao ler arquivo: {}", erro),
    }
}

Solução 2: Usar if let para Casos Simples

Quando você só se importa com o caso de sucesso:

fn main() {
    let numeros = vec![1, 2, 3];

    if let Some(valor) = numeros.get(0) {
        println!("Primeiro: {}", valor);
    }

    if let Ok(conteudo) = std::fs::read_to_string("config.txt") {
        println!("Conteúdo: {}", conteudo);
    } else {
        println!("Usando configuração padrão");
    }
}

Solução 3: Usar unwrap_or e Variantes

Forneça um valor padrão em vez de entrar em panic:

fn main() {
    let numeros = vec![1, 2, 3];

    // Valor padrão fixo
    let valor = numeros.get(10).unwrap_or(&0);
    println!("Valor: {}", valor);

    // Valor padrão calculado (lazy — só executa se necessário)
    let valor = numeros.get(10).unwrap_or_else(|| {
        println!("Usando padrão");
        &0
    });

    // Para Result:
    let conteudo = std::fs::read_to_string("config.txt")
        .unwrap_or_else(|_| String::from("configuração padrão"));

    // unwrap_or_default — usa Default::default()
    let numero: i32 = "abc".parse().unwrap_or_default(); // 0
}

Solução 4: Usar o Operador ? (Propagação de Erros)

O operador ? é a forma idiomática do Rust para propagar erros:

use std::fs;
use std::io;

fn ler_configuracao() -> Result<String, io::Error> {
    let conteudo = fs::read_to_string("config.txt")?;  // Propaga o erro
    Ok(conteudo.trim().to_string())
}

fn obter_porta() -> Result<u16, Box<dyn std::error::Error>> {
    let config = ler_configuracao()?;
    let porta: u16 = config.parse()?;  // ? também funciona com outros erros
    Ok(porta)
}

fn main() {
    match obter_porta() {
        Ok(porta) => println!("Porta: {}", porta),
        Err(e) => eprintln!("Erro: {}", e),
    }
}

O ? pode ser usado em funções que retornam Result ou Option. Ele “desempacota” o valor de sucesso ou retorna o erro imediatamente.

Solução 5: Usar map, and_then e Combinadores

Encadeie operações de forma funcional:

fn main() {
    let input = "42";

    // map transforma o valor interno
    let resultado: Option<i32> = input.parse::<i32>().ok().map(|n| n * 2);
    println!("{:?}", resultado); // Some(84)

    // and_then encadeia operações que retornam Option/Result
    let valor = Some("42")
        .and_then(|s| s.parse::<i32>().ok())
        .map(|n| n + 10)
        .unwrap_or(0);
    println!("{}", valor); // 52

    // filter descarta valores que não atendem a condição
    let positivo = Some(-5_i32).filter(|n| *n > 0);
    println!("{:?}", positivo); // None
}

Solução 6: Usar expect() com Mensagem Clara

Se você precisa de unwrap (em testes ou situações seguras), use expect() com uma mensagem explicativa:

fn main() {
    let porta: u16 = std::env::var("PORT")
        .expect("Variável PORT deve estar definida")
        .parse()
        .expect("PORT deve ser um número válido");
}

expect() entra em panic como unwrap(), mas com uma mensagem customizada que facilita a depuração.

Quando unwrap() é Aceitável

SituaçãoAceitável?
Testes (#[test])Sim
Exemplos e protótiposSim
Valor garantido pela lógicaSim, mas prefira expect()
Código de produçãoNao — use ?, match ou combinadores
Input do usuárioNunca

Veja Também