Неисправимые ошибки с panic!
Иногда в коде возникают критические ошибки, и от этого никуда не деться. Для этих случаев у Rust есть макрос panic!. На практике существует два способа вызвать панику: путём выполнения действия, которое вызывает панику (например, обращение к массиву за пределами его размера) или путём явного вызова макроса panic!. В обоих случаях мы вызываем панику в нашей программе. По умолчанию, паника выводит сообщение об ошибке, раскручивает и очищает стек вызовов и завершает работу. С помощью переменной окружения вы также можете заставить Rust отображать стек вызовов при возникновении паники, чтобы было легче отследить источник паники.
Раскрутить стек или прервать исполнение программы?
By default, when a panic occurs, the program starts unwinding, which means Rust walks back up the stack and cleans up the data from each function it encounters. However, walking back and cleaning up is a lot of work. Rust therefore allows you to choose the alternative of immediately aborting, which ends the program without cleaning up.
Память, которую использовала программа, должна быть очищена операционной системой. Если в вашем проекте нужно сделать исполняемый файл маленьким настолько, насколько это возможно, вы можете прямо указать программе прерывать исполнение в случае паники, добавив panic = 'abort' в соответствующие разделы [profile] вашего файла Cargo.toml. Например, если вы хотите прерывать панику в релизном режиме, добавьте это:
[profile.release]
panic = 'abort'
Давайте попробуем вызвать panic! в простой программе:
fn main() {
panic!("критическая ошибка");
}
При запуске программы вы увидите подобный вывод:
$ 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
Выполнение макроса panic! вызывает сообщение об ошибке, представленное в двух последних строчках выше. Первая строчка содержит сообщение паники и место в исходном коде, где возникла паника: запись src/main.rs:2:5 указывает на второй строчки пятый символ внутри файла src/main.rs.
В нашем случае, сообщение об ошибке указывает на наш собственный код, и если мы перейдём к этой строке, мы увидим вызов макроса panic!. В других случаях вызов panic! мог бы произойти в стороннем коде, вызываемом нашим, и тогда имя файла и номер строки указывали бы именно туда, где конкретно вызывается макрос panic!, а не на тот код, вызов которого привёл к вызову panic!.
Мы можем использовать развёртку вызовов функций, приводящих к вызову panic!, чтобы выяснить, какая часть нашего кода вызывает проблему. Чтобы понять, как использовать развёртку вызова panic!, давайте рассмотрим другой пример и посмотрим, каково это, когда panic! вызывается не напрямую через вызов макроса, а в библиотечном коде, содержащем баг. В Листинге 9-1 приведен некоторый код, который пытается получить доступ элементу вектора по индексу, лежащему за пределами вектора.
fn main() {
let v = vec![1, 2, 3];
v[99];
}
panic!Здесь мы пытаемся получить доступ к 100му элементу вектора (который находится по индексу 99, потому что индексирование начинается с нуля), но вектор имеет только 3 элемента. В этой ситуации, Rust будет вызывать панику. Использование [] должно возвращать элемент, но если вы передадите неверный индекс, Rust не сможет найти ничего, что можно было бы корректно вернуть.
В языке C, попытка прочесть за пределами конца структуры данных (в нашем случае, вектора) приведёт к неопределённому поведению. А именно, вы всё-таки получите значение, которое находится в том месте памяти компьютера, которое соответствовало бы этому элементу в структуре данных — даже несмотря на то, что это место к ней относиться никак не будет. Это называется чтением за границами буфера (buffer overread) — уязвимостью в виде возможности чтения злоумышленником данных, к которым у него не должно быть доступа, вызванной возможностью читать произвольные области памяти с помощью некорректных индексов.
Чтобы защищать вашу программу от такого рода уязвимостей при попытке прочитать элемент по некорректному индексу, Rust останавливает исполнение и отказывается продолжать работу программы. Посмотрим, как это выглядит:
$ 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
This error points at line 4 of our main.rs where we attempt to access index 99 of the vector in v.
The note: line tells us that we can set the RUST_BACKTRACE environment variable to get a backtrace of exactly what happened to cause the error. A backtrace is a list of all the functions that have been called to get to this point. Backtraces in Rust work as they do in other languages: The key to reading the backtrace is to start from the top and read until you see files you wrote. That’s the spot where the problem originated. The lines above that spot are code that your code has called; the lines below are code that called your code. These before-and-after lines might include core Rust code, standard library code, or crates that you’re using. Let’s try to get a backtrace by setting the RUST_BACKTRACE environment variable to any value except 0. Listing 9-2 shows output similar to what you’ll see.
$ 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.
panic! displayed when the environment variable RUST_BACKTRACE is setСколько текста! Вывод, который вы увидите, может отличаться от представленного выше, в зависимости от вашей операционной системы и версии Rust. Для того, чтобы получить развёртку вызовов с этой информацией, должны быть включены настройки отладки. Настройки отладки включены по умолчанию при использовании cargo build или cargo run, если вы не указываете флаг --release.
В развёртке вызовов из Листинга 9-2, строка 6: ... указывает на строчку в нашем проекте, которая и вызывала проблему: строчка 4 файла src/main.rs. Если мы не хотим, чтобы наша программа паниковала, мы должны начать анализ с места, на которое указывает первая строка с упоминанием нашего собственного файла. В Листинге 9-1, где мы для демонстрации развёртки вызовов сознательно написали код, который паникует, способ исправления паники состоит в том, чтобы не запрашивать элемент за пределами диапазона значений индексов вектора. Когда вы встретитесь с паникой в своих будущих программах, вам нужно будет выяснить, что над чем конкретно делает код, и что он должен делать по задумке.
Мы вернёмся к обсуждению макроса panic! и того, когда нам следует и не следует его использовать для обработки ошибок, в разделе “Когда следует использовать panic!?” этой главы. Далее мы рассмотрим, как можно продолжить исполнение программы после ошибки, воспользовавшись типом Result.