Невосстанавливаемые ошибки с panic!
Иногда в коде происходят плохие вещи, и вы ничего не можете с этим сделать. В
таких случаях в Rust есть макрос panic!. На практике вызвать панику можно
двумя способами: выполнить действие, из-за которого наш код паникует
(например, обратиться к элементу массива за его концом), или явно вызвать
макрос panic!. В обоих случаях мы вызываем панику в нашей программе. По
умолчанию такие паники выводят сообщение об ошибке, раскручивают стек, очищают
его и завершают работу. С помощью переменной окружения можно также попросить
Rust показать стек вызовов при возникновении паники, чтобы было проще найти ее
источник.
Раскручивание стека или аварийное завершение в ответ на панику
По умолчанию при возникновении паники программа начинает раскручивание стека: Rust поднимается обратно по стеку и очищает данные из каждой встреченной функции. Однако такой обратный проход и очистка требуют большой работы. Поэтому Rust позволяет выбрать альтернативу – немедленное аварийное завершение, которое завершает программу без очистки.
Память, которую использовала программа, затем должна будет очистить
операционная система. Если в вашем проекте нужно сделать итоговый бинарный
файл как можно меньше, можно переключиться с раскручивания стека на аварийное
завершение при панике, добавив panic = 'abort' в соответствующие разделы
[profile] файла Cargo.toml. Например, если вы хотите аварийно завершать
программу при панике в режиме release, добавьте следующее:
[profile.release]
panic = 'abort'
Попробуем вызвать panic! в простой программе:
fn main() {
panic!("crash and burn");
}
Когда вы запустите программу, то увидите примерно следующее:
$ 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, потому что индексация начинается с нуля), но вектор содержит
только три элемента. В этой ситуации Rust запаникует. Использование []
предполагает возврат элемента, но если передать недопустимый индекс, здесь нет
элемента, который Rust мог бы корректно вернуть.
В C попытка прочитать данные за концом структуры данных является неопределенным поведением. Вы можете получить то, что находится в области памяти, которая соответствовала бы этому элементу в структуре данных, даже если эта память не принадлежит данной структуре. Это называется чтением за пределами буфера и может привести к уязвимостям безопасности, если злоумышленник сможет управлять индексом так, чтобы прочитать данные, доступ к которым ему не должен быть разрешен и которые хранятся после структуры данных.
Чтобы защитить программу от такого рода уязвимости, 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
Эта ошибка указывает на строку 4 файла main.rs, где мы пытаемся обратиться к
индексу 99 вектора v.
Строка note: сообщает, что можно установить переменную окружения
RUST_BACKTRACE, чтобы получить обратную трассировку того, что именно
произошло и привело к ошибке. Обратная трассировка – это список всех
функций, которые были вызваны, чтобы дойти до этой точки. Обратные трассировки
в Rust работают так же, как и в других языках: главное при чтении трассировки
– начать сверху и читать до тех пор, пока вы не увидите файлы, которые писали
сами. Это и есть место, где возникла проблема. Строки выше этого места – это
код, который был вызван вашим кодом; строки ниже – код, который вызвал ваш
код. Эти строки до и после могут включать код ядра Rust, код стандартной
библиотеки или крейты, которые вы используете. Попробуем получить обратную
трассировку, установив переменную окружения RUST_BACKTRACE в любое значение,
кроме 0. Листинг 9-2 показывает вывод, похожий на тот, который вы увидите.
$ 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! и показанная, когда установлена переменная окружения RUST_BACKTRACEВывода получилось много! Точный вывод может отличаться в зависимости от вашей
операционной системы и версии Rust. Чтобы получить обратные трассировки с такой
информацией, должны быть включены отладочные символы. Отладочные символы
включены по умолчанию при использовании cargo build или cargo run без флага
--release, как в нашем случае.
В выводе листинга 9-2 строка 6 обратной трассировки указывает на строку в нашем проекте, которая вызывает проблему: строку 4 файла src/main.rs. Если мы не хотим, чтобы наша программа паниковала, расследование следует начинать с места, на которое указывает первая строка с упоминанием файла, написанного нами. В листинге 9-1 мы намеренно написали код, который запаникует, и способ исправить панику – не запрашивать элемент за пределами диапазона индексов вектора. Когда ваш код будет паниковать в будущем, вам нужно будет выяснить, какое действие выполняет код, с какими значениями это вызывает панику и что код должен делать вместо этого.
Мы вернемся к panic! и к тому, когда следует и когда не следует использовать
panic! для обработки ошибочных условий, позже в этой главе, в разделе
«Использовать panic! или не использовать
panic!». Далее мы рассмотрим, как
восстанавливаться после ошибки с помощью Result.