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.
Causando um panic de propósito
Seção intitulada “Causando um panic de propósito”fn main() { panic!("algo deu muito errado");}thread 'main' (12345) panicked at src/main.rs:2:5:algo deu muito erradonote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceO 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.
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).
Coisas que causam panic sem você chamar panic!
Seção intitulada “Coisas que causam panic sem você chamar panic!”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));}fn main() { let value: Option<i32> = None; println!("{}", value.unwrap()); // panic: called `Option::unwrap()` on a `None` value}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));}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.
panic! ou Result?
Seção intitulada “panic! ou Result?”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 Resultfn 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: panicfn get_first(numbers: &[i32]) -> i32 { if numbers.is_empty() { panic!("get_first foi chamada com uma slice vazia"); } numbers[0]}unwrap() e expect() são atalhos para o panic
Seção intitulada “unwrap() e expect() são atalhos para o panic”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}");}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 forfalse.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 dematchpara 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: 80o dia 3 é quarta-feiraAqui 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: 80A 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.
Prática
Seção intitulada “Prática”1. Um número de porta com expect
Seção intitulada “1. Um número de porta com expect”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 80802. Dividindo a conta
Seção intitulada “2. Dividindo a conta”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 30panic!para o programa na hora, com uma mensagem de erro.- Algumas operações da biblioteca padrão (índice inválido em um array,
.unwrap()emNone, divisão por zero) causam panic automaticamente. - Use
panic!para bugs e regras que nunca deveriam ser quebradas; useResultpara 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!eunreachable!causam um panic de propósito quando algo que você acreditava ser verdade acaba se mostrando falso.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”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
Para saber mais
Seção intitulada “Para saber mais”- Unrecoverable Errors with panic! — The Rust Book (em inglês): o que acontece durante um panic, o unwinding (desenrolar da pilha) e como ler um backtrace.
- To panic! or Not to panic! — The Rust Book (em inglês): as orientações oficiais para escolher entre
panic!eResult, com exemplos. - panic! — Rust by Example (em inglês): um exemplo curto de panic, mostrando que a memória continua sendo liberada enquanto o programa desenrola a pilha.
assert_eq!— documentação da std (em inglês): como funcionam as macros de asserção, incluindo mensagens personalizadas.panic!— documentação da std (em inglês): a referência do própriopanic!, como ele se relaciona com ounwrape quando um panic é a ferramenta certa.