Pular para o conteúdo

Panic em Rust

Um panic é a forma que o Rust tem de parar um programa na hora quando algo deu errado sem chance de conserto. Se você acompanhou as páginas anteriores, provavelmente já causou alguns sem querer: acessar um índice além do fim de um array ou chamar .unwrap() em um None causam panic.

fn main() {
panic!("algo deu muito errado");
}
thread 'main' (12345) panicked at src/main.rs:2:5:
algo deu muito errado
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

O número entre parênteses, aqui (12345), é o ID da thread. Ele muda a cada execução, e versões mais antigas do Rust não o mostram.

▶ Experimente você mesmo

Quando isso roda, o programa mostra essa mensagem e para. Nada depois do panic! é executado.

As linhas em inglês são do próprio Rust: ele avisa que a thread main entrou em panic em src/main.rs, linha 2, e sugere rodar com a variável de ambiente RUST_BACKTRACE=1 para ver o caminho completo das chamadas (o backtrace).

Algumas operações que você já conhece entram em panic sozinhas se algo estiver errado. Cada exemplo abaixo recebe o valor problemático por um parâmetro de função, em vez de um valor fixo no código. Com um índice fixo ou um 0 escrito ali mesmo, o compilador muitas vezes percebe, já na compilação, que a operação sempre vai falhar, e se recusa a compilar em vez de deixar o programa rodar e entrar em panic.

fn get(numbers: &[i32], index: usize) -> i32 {
numbers[index] // panic: index out of bounds (índice fora dos limites)
}
fn main() {
let numbers = [1, 2, 3];
println!("{}", get(&numbers, 10));
}
▶ Experimente você mesmo
fn main() {
let value: Option<i32> = None;
println!("{}", value.unwrap()); // panic: called `Option::unwrap()` on a `None` value
}
▶ Experimente você mesmo
fn divide(a: i32, b: i32) -> i32 {
a / b // panic: attempt to divide by zero (tentativa de dividir por zero)
}
fn main() {
let a = 10;
let b = 0;
println!("{}", divide(a, b));
}
▶ Experimente você mesmo

Em todos esses casos, o Rust se recusa a deixar o programa continuar em um estado que não faz sentido, em vez de devolver lixo silenciosamente.

Esta é a decisão que você vai tomar com frequência: essa falha deve derrubar o programa ou quem chama a função deve ter a chance de tratá-la?

  • panic!: para bugs e situações que nunca deveriam acontecer se o código estiver correto. Não existe um jeito sensato de se recuperar, então parar na hora (antes de causar mais estrago) é a opção mais segura.
  • Result (visto na página anterior): para falhas que quem chama pode esperar e querer tratar, como uma entrada inválida do usuário, um arquivo que não existe ou uma requisição de rede que falhou.
// Entrada inválida do usuário: dá para se recuperar, use Result
fn parse_age(input: &str) -> Result<u32, String> {
input.parse().map_err(|_| String::from("não é uma idade válida"))
}
// Uma regra que nunca deveria ser quebrada se o código estiver correto: panic
fn get_first(numbers: &[i32]) -> i32 {
if numbers.is_empty() {
panic!("get_first foi chamada com uma slice vazia");
}
numbers[0]
}

Você já viu o .unwrap() tanto em Option quanto em Result. Na prática, ele é só um atalho para “me dê o valor ou entre em panic se não houver um”. O .expect("mensagem") faz a mesma coisa, mas permite anexar uma mensagem mais útil:

fn main() {
let input = "42";
let number: i32 = input.parse().expect("a entrada deveria ser um número válido");
println!("{number}");
}
▶ Experimente você mesmo

Se a conversão falhar, a mensagem do panic inclui o seu texto, o que facilita muito a depuração em comparação com a mensagem genérica do .unwrap().

Conferindo suas suposições: assert!, assert_eq! e unreachable!

Seção intitulada “Conferindo suas suposições: assert!, assert_eq! e unreachable!”

Às vezes você sabe que algo precisa ser verdade em determinado ponto do código. O Rust tem algumas macros pequenas que transformam essa certeza em uma verificação. Se a verificação falhar, elas causam um panic com uma mensagem clara, e o bug aparece bem onde começou, em vez de gerar resultados estranhos mais adiante.

  • assert!(condição, "mensagem") causa um panic se a condição for false.
  • assert_eq!(esquerda, direita) causa um panic se os dois valores não forem iguais, e imprime os dois.
  • unreachable!("mensagem") marca um código que nunca deveria rodar, como um braço de match para um valor que não pode acontecer.
fn average(numbers: &[i32]) -> i32 {
assert!(!numbers.is_empty(), "average precisa de pelo menos um número");
let mut total = 0;
for n in numbers {
total += n;
}
total / numbers.len() as i32
}
fn day_name(day: u8) -> &'static str {
match day {
1 => "segunda-feira",
2 => "terça-feira",
3 => "quarta-feira",
4 => "quinta-feira",
5 => "sexta-feira",
6 => "sábado",
7 => "domingo",
_ => unreachable!("o dia precisa ser de 1 a 7, veio {day}"),
}
}
fn main() {
let avg = average(&[70, 80, 90]);
assert_eq!(avg, 80);
println!("média: {avg}");
println!("o dia 3 é {}", day_name(3));
}

Saída:

média: 80
o dia 3 é quarta-feira
▶ Experimente você mesmo

Aqui todas as verificações passam, então o programa roda normalmente. Agora troque a lista por [70, 80, 95] no Playground. A média vira 81, e o assert_eq! para o programa mostrando os dois valores lado a lado:

thread 'main' (12345) panicked at src/main.rs:26:5:
assertion `left == right` failed
left: 81
right: 80

A mensagem do Rust diz que a asserção left == right (“esquerda == direita”) falhou e mostra o valor de cada lado. Experimente também average(&[]) ou day_name(9), e você vai ver as suas próprias mensagens no panic.

Essas verificações sempre rodam, mesmo em uma build de release, então mantenha-as baratas. Você vai reencontrar o assert_eq! na página de Testes em Rust, onde ele é o principal jeito de um teste dizer “isto deveria ser igual àquilo”. Existe também o todo!(), que causa um panic com a mensagem “not yet implemented” (“ainda não implementado”). Ele é útil como corpo provisório enquanto você esboça uma função que vai terminar depois.

Você tem um valor de configuração let port_text = "8080";. Transforme-o em um u16 com .parse() e .expect(...), usando uma mensagem que explique o que deu errado. Depois, imprima Escutando na porta 8080. Dica: a variável precisa de um tipo, let port: u16 = ..., para o parse saber o que produzir. Em seguida, troque o texto por "80a0" para ver a sua mensagem no panic.

Ver solução
fn main() {
let port_text = "8080";
let port: u16 = port_text
.parse()
.expect("a porta na configuração deveria ser um número de 0 a 65535");
println!("Escutando na porta {port}");
}

Saída:

Escutando na porta 8080
▶ Experimente você mesmo

Escreva fn split_bill(total: u32, people: u32) -> u32, que devolve quanto cada pessoa paga. Dividir entre zero pessoas é um bug no código que chama a função, então use assert! com uma mensagem clara para pegar esse caso. Imprima a parte de cada um para um total de 120 e 4 pessoas. Dica: coloque o assert! na primeira linha da função, antes da divisão.

Ver solução
fn split_bill(total: u32, people: u32) -> u32 {
assert!(people > 0, "split_bill precisa de pelo menos uma pessoa");
total / people
}
fn main() {
let share = split_bill(120, 4);
println!("Cada pessoa paga {share}");
}

Saída:

Cada pessoa paga 30
▶ Experimente você mesmo
  • panic! para o programa na hora, com uma mensagem de erro.
  • Algumas operações da biblioteca padrão (índice inválido em um array, .unwrap() em None, divisão por zero) causam panic automaticamente.
  • Use panic! para bugs e regras que nunca deveriam ser quebradas; use Result para falhas que quem chama pode tratar.
  • .expect("mensagem") é como o .unwrap(), mas com uma mensagem que torna o panic mais fácil de depurar.
  • assert!, assert_eq! e unreachable! causam um panic de propósito quando algo que você acreditava ser verdade acaba se mostrando falso.

Teste o que aprendeu

1. O que acontece quando você chama panic!?

2. Segundo a regra geral desta página, quando você deve usar panic! em vez de Result?

3. Qual é a diferença entre .unwrap() e .expect('mensagem')?

Pontuação: 0 / 3