Erros Irrecuperáveis com panic!
Às vezes coisas ruins acontecem no seu código, e não há nada que você possa fazer
sobre isso. Nesses casos, Rust tem a macro panic!
. Quando ela é executada, seu
programa vai imprimir uma mensagem de falha, resolver e limpar a pilha e então
terminar. A situação mais comum em que isso acontece é quando algum tipo de bug
foi detectado, e não é claro para o programador como tratar esse erro.
Resolver a Pilha ou Abortar em Resposta a
panic!
Por padrão, quando acontece um
panic!
, o programa começa a resolver a pilha, o que significa que Rust percorre a pilha e limpa os dados de cada função que encontra. Mas essa limpeza dá bastante trabalho. A alternativa é imediatamente abortar, que termina o programa sem fazer a limpeza. A memória que o programa estava usando vai então precisar ser limpada pelo sistema operacional. Se no seu projeto você precisa tornar o binário final o menor possível, você pode deixar de resolver e sim abortar no panic adicionandopanic = 'abort'
à seção apropriadada de[profile]
no seu arquivo Cargo.toml. Por exemplo, se você quiser abortar no panic no modo de release, adicione isso:[profile.release] panic = 'abort'
Vamos tentar chamar panic!
em um programa simples:
Arquivo: src/main.rs
fn main() { panic!("Quebra tudo"); }
Quando você roda o programa, verá algo como isso:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs
Running `target/debug/panic`
thread 'main' panicked at 'Quebra tudo', src/main.rs:2
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
A chamada a panic!
causa a mensagem de erro contida nas últimas três linhas.
A primeira linha mostra nossa mensagem de pânico e a posição no código fonte
em que ocorreu o pânico: src/main.rs:2 indica que é a segunda linha do nosso
arquivo src/main.rs.
Nesse caso, a linha indicada é parte do nosso código, e se formos àquela linha
veremos a chamada à macro panic!
. Em outros casos, a chamada a panic!
pode
estar em código que nosso código chama. O nome do arquivo e número de linha
reportado pela mensagem de erro será no código de outra pessoa quando a macro
panic!
for chamada, não a linha do nosso código que eventualmente levou a chamada
de panic!
. Podemos usar o backtrace das funções de onde veio a chamada a panic!
para entender qual parte de nosso código está causando o problema. Vamos discutir
o que é um backtrace em seguida.
Usando um Backtrace de panic!
Vamos ver outro exemplo para ver o que acontece quando uma chamada panic!
vem de uma
biblioteca por causa de um bug no nosso código em vez de nosso código chamar
a macro diretamente. A Listagem 9-1 tem código que tenta acessar um elemento em
um vetor por meio de um índice:
Arquivo: src/main.rs
fn main() { let v = vec![1, 2, 3]; v[99]; }
Aqui, estamos tentando acessar o centésimo elemento (centésimo pois o índice
começa em zero) de nosso vetor, mas ele só tem três elementos. Nesse caso, Rust
entrará em pânico. Supostamente []
devolve um elemento, mas se você passa um
índice inválido, não há elemento que Rust possa retornar que fosse correto.
Outras linguagens, como C, vão tentar te dar exatamente o que você pediu nessa situação, mesmo que não seja o que você quer: você vai receber o que quer que esteja na localização na memória que corresponderia àquele elemento no vetor, mesmo que a memória não pertença ao vetor. Isso se chama um buffer overread e pode levar a vulnerabilidades de segurança se um agressor for capaz de manipular o índice de forma a ler dados guardados depois do array aos quais ele não deveria ter acesso.
Para proteger seu programa desse tipo de vulnerabilidade, se você tentar ler um elemento em um índice que não exista, Rust vai parar a execução e se recusar a continar. Vamos fazer isso e ver o que acontece:
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
100', /stable-dist-rustc/build/src/libcollections/vec.rs:1362
note: Run with `RUST_BACKTRACE=1` for a backtrace.
error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
Esse erro aponta para um arquivo que não escrevemos, libcollections/vec.rs.
Essa é a implementação de Vec<T>
na biblioteca padrão. O código que roda
quando usamos []
em nosso vetor v
está em libcollections/vec.rs, e é aí
que o panic!
está de fato acontecendo.
A próxima linha nos diz que podemos definir a variável de ambiente RUST_BACKTRACE
para ter um backtrace (rastro) do que aconteceu, exatamente, para causar o erro. Um
backtrace é uma lista de todas as funções que foram chamadas para chegar a esse
ponto. Backtraces em Rust funcionam como em outras linguagens: a chave para ler
o backtrace é começar do topo e ler até você ver os arquivos que você escreveu.
Esse é o ponto em que o problema se originou. As linhas acima das que mencionam seu
código são funções que você chamou; as linhas abaixo são funções que chamaram seu
código. Essas linhas podem incluir código do núcleo do Rust, código das bibliotecas
padrão, ou crates que você está usando. Vamos tentar ver um backtrace: a Listagem 9-2
mostra uma saída semelhante a o que você verá:
$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', /stable-dist-rustc/build/src/libcollections/vec.rs:1392
stack backtrace:
1: 0x560ed90ec04c - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed
at /stable-dist-rustc/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42
2: 0x560ed90ee03e - std::panicking::default_hook::{{closure}}::h59672b733cc6a455
at /stable-dist-rustc/build/src/libstd/panicking.rs:351
3: 0x560ed90edc44 - std::panicking::default_hook::h1670459d2f3f8843
at /stable-dist-rustc/build/src/libstd/panicking.rs:367
4: 0x560ed90ee41b - std::panicking::rust_panic_with_hook::hcf0ddb069e7abcd7
at /stable-dist-rustc/build/src/libstd/panicking.rs:555
5: 0x560ed90ee2b4 - std::panicking::begin_panic::hd6eb68e27bdf6140
at /stable-dist-rustc/build/src/libstd/panicking.rs:517
6: 0x560ed90ee1d9 - std::panicking::begin_panic_fmt::abcd5965948b877f8
at /stable-dist-rustc/build/src/libstd/panicking.rs:501
7: 0x560ed90ee167 - rust_begin_unwind
at /stable-dist-rustc/build/src/libstd/panicking.rs:477
8: 0x560ed911401d - core::panicking::panic_fmt::hc0f6d7b2c300cdd9
at /stable-dist-rustc/build/src/libcore/panicking.rs:69
9: 0x560ed9113fc8 - core::panicking::panic_bounds_check::h02a4af86d01b3e96
at /stable-dist-rustc/build/src/libcore/panicking.rs:56
10: 0x560ed90e71c5 - <collections::vec::Vec<T> as core::ops::Index<usize>>::index::h98abcd4e2a74c41
at /stable-dist-rustc/build/src/libcollections/vec.rs:1392
11: 0x560ed90e727a - panic::main::h5d6b77c20526bc35
at /home/you/projects/panic/src/main.rs:4
12: 0x560ed90f5d6a - __rust_maybe_catch_panic
at /stable-dist-rustc/build/src/libpanic_unwind/lib.rs:98
13: 0x560ed90ee926 - std::rt::lang_start::hd7c880a37a646e81
at /stable-dist-rustc/build/src/libstd/panicking.rs:436
at /stable-dist-rustc/build/src/libstd/panic.rs:361
at /stable-dist-rustc/build/src/libstd/rt.rs:57
14: 0x560ed90e7302 - main
15: 0x7f0d53f16400 - __libc_start_main
16: 0x560ed90e6659 - _start
17: 0x0 - <unknown>
Isso é bastante saída! A saída exata que você recebe pode ser diferente dependendo do seu sistema operacional e versão de Rust. Para conseguir backtraces com essa informação, símbolos de debug devem estar ativados. Símbolos de debug estão ativados por padrão quando usamos cargo build ou cargo run sem a opção de --release, como temos aqui.
Na saída da Listagem 9-2, a linha 11 do backtrace aponta para a linha no nosso projeto que está causando o problema: src/main.rs na linha 4. Se não queremos que nosso programa entre em pânico, a localização apontada pela primeira linha mencionando um arquivo que escrevemos é onde deveríamos começar a investigar para entender como chegamos nesse ponto com valores que causaram o pânico. Na lista 9-1 onde nós deliberadamente escrevemos código que causaria pânico para demonstrar como usar backtraces, a forma de consertar o pânico é não requisitar um elemento no índice 100 de um vetor que contém apenas três itens. Quando seu código entrar em pânico no futuro, você precisará descobrir quais ações o seu código está tomando, e com quais valores, que estão causando o pânico, e o que deveria ser feito em vez disso.
Voltaremos ao panic!
e veremos mais adiante no capítulo quando usá-lo, ou não,
para lidar com erros. Em seguida, olharemos como se recuperar de um erro usando
Result
.