Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Erros Irrecuperáveis com panic!

Às vezes, coisas ruins acontecem no seu código, e não há nada que você possa fazer a respeito. Nesses casos, Rust oferece a macro panic!. Na prática, existem duas formas de causar um panic: executando uma ação que faça o código entrar em panic, como acessar um array além do fim, ou chamando explicitamente a macro panic!. Em ambos os casos, provocamos um panic no programa. Por padrão, esses panics exibem uma mensagem de falha, fazem unwind, limpam a stack e encerram o programa. Por meio de uma variável de ambiente, você também pode fazer Rust exibir a call stack quando ocorre um panic, para facilitar a localização da origem do problema.

Fazendo Unwind da Stack ou Abortando em Resposta a um Panic

Por padrão, quando ocorre um panic, o programa começa a fazer unwind, o que significa que Rust percorre a stack de volta e limpa os dados de cada função encontrada. No entanto, percorrer a stack e limpar tudo dá trabalho. Por isso, Rust permite que você escolha a alternativa de abortar imediatamente, o que encerra o programa sem fazer limpeza.

A memória que o programa estava usando precisará então ser liberada pelo sistema operacional. Se, no seu projeto, você precisar tornar o binário resultante o menor possível, pode trocar de unwind para abort ao ocorrer um panic adicionando panic = 'abort' às seções [profile] apropriadas do arquivo Cargo.toml. Por exemplo, se você quiser abortar ao ocorrer um panic no modo release, adicione isto:

[profile.release]
panic = 'abort'

Vamos tentar chamar panic! em um programa simples:

Filename: src/main.rs
fn main() {
    panic!("crash and burn");
}

Ao executar o programa, você verá algo assim:

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:2:5:
crash and burn
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

A chamada a panic! causa a mensagem de erro contida nas duas últimas linhas. A primeira linha mostra nossa mensagem de panic e o local do código-fonte em que ele ocorreu: src/main.rs:2:5 indica a segunda linha e o quinto caractere do nosso arquivo src/main.rs.

Neste caso, a linha indicada faz parte do nosso código e, se formos até ela, veremos a chamada à macro panic!. Em outros casos, a chamada a panic! pode estar no código que o nosso código chamou, e o nome do arquivo e o número da linha informados pela mensagem de erro serão do código de outra pessoa onde a macro panic! foi chamada, e não da linha do nosso código que acabou levando até essa chamada.

Podemos usar o backtrace das funções de onde veio a chamada panic! para descobrir qual parte do nosso código está causando o problema. Para entender como usar um backtrace de panic!, vamos analisar outro exemplo e ver como é quando uma chamada panic! vem de uma biblioteca por causa de um bug no nosso código, em vez de vir diretamente de uma chamada nossa à macro. A Listagem 9-1 tem um código que tenta acessar um índice de um vetor além do intervalo de índices válidos.

Filename: src/main.rs
fn main() {
    let v = vec![1, 2, 3];

    v[99];
}
Listing 9-1: Tentando acessar um elemento além do fim de um vetor, o que causará uma chamada a panic!

Aqui, estamos tentando acessar o centésimo elemento do nosso vetor, que está no índice 99, porque a indexação começa em zero, mas o vetor tem apenas três elementos. Nessa situação, Rust entra em panic. Usar [] deveria retornar um elemento, mas, se você passar um índice inválido, não existe nenhum elemento que Rust pudesse retornar aqui de forma correta.

Em C, tentar ler além do fim de uma estrutura de dados é um comportamento indefinido. Você pode acabar obtendo qualquer coisa que esteja naquela posição de memória correspondente ao elemento na estrutura, mesmo que a memória não pertença a ela. Isso é chamado de buffer overread e pode levar a vulnerabilidades de segurança se uma pessoa atacante conseguir manipular o índice de maneira a ler dados que não deveria ter permissão para acessar e que estejam armazenados depois da estrutura de dados.

Para proteger seu programa contra esse tipo de vulnerabilidade, se você tentar ler um elemento em um índice inexistente, Rust vai interromper a execução e se recusar a continuar. Vamos tentar para ver:

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.27s
     Running `target/debug/panic`

thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Esse erro aponta para a linha 4 do nosso main.rs, onde tentamos acessar o índice 99 do vetor v.

A linha note: nos diz que podemos definir a variável de ambiente RUST_BACKTRACE para obter um backtrace exato do que aconteceu para causar o erro. Um backtrace é uma lista de todas as funções chamadas até chegar a esse ponto. Backtraces em Rust funcionam como em outras linguagens: a chave para lê-los é começar do topo e continuar até encontrar arquivos que você escreveu. Esse é o ponto onde o problema se originou. As linhas acima desse ponto são códigos que seu código chamou; as linhas abaixo são códigos que chamaram o seu código. Essas linhas antes e depois podem incluir código interno do Rust, código da biblioteca padrão ou crates que você está usando. Vamos tentar obter um backtrace definindo a variável de ambiente RUST_BACKTRACE para qualquer valor diferente de 0. A Listagem 9-2 mostra uma saída parecida com a que você verá.

$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
   0: rust_begin_unwind
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
   1: core::panicking::panic_fmt
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
   2: core::panicking::panic_bounds_check
             at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
   3: <usize as core::slice::index::SliceIndex<[T]>>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:274:10
   4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:16:9
   5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3361:9
   6: panic::main
             at ./src/main.rs:4:6
   7: core::ops::function::FnOnce::call_once
             at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Listing 9-2: O backtrace gerado por uma chamada a panic!, exibido quando a variável de ambiente RUST_BACKTRACE está definida

É bastante saída! A saída exata pode ser diferente dependendo do seu sistema operacional e da versão do Rust. Para obter backtraces com essas informações, os símbolos de depuração precisam estar habilitados. Eles ficam habilitados por padrão ao usar cargo build ou cargo run sem a flag --release, como estamos fazendo aqui.

Na saída da Listagem 9-2, a linha 6 do backtrace aponta para a linha do nosso projeto que está causando o problema: a linha 4 de src/main.rs. Se não quisermos que o programa entre em panic, devemos começar a investigação no local indicado pela primeira linha que menciona um arquivo escrito por nós. Na Listagem 9-1, em que escrevemos deliberadamente um código que causaria panic, a forma de corrigir o problema é não solicitar um elemento além do intervalo de índices do vetor. Quando seu código entrar em panic no futuro, você vai precisar descobrir que ação ele está realizando, com quais valores, para causar o panic, e o que ele deveria fazer em vez disso.

Voltaremos a panic! e a quando devemos ou não usá-lo para lidar com condições de erro na seção “Para panic! ou não para panic! mais adiante neste capítulo. A seguir, veremos como se recuperar de um erro usando Result.