Неисправимые ошибки и panic!
Иногда в коде случается что-то плохое, и с этим ничего не поделать. На такие
случаи в Rust есть макрос panic!. На практике вызвать панику можно двумя
способами: сделать что-то, отчего код запаникует (например, обратиться за
конец массива), или явно вызвать макрос panic!. В обоих случаях в программе
возникает паника. По умолчанию при панике программа выводит сообщение об
ошибке, раскручивает и очищает стек и завершается. С помощью переменной
окружения можно также попросить Rust при панике показывать стек вызовов —
так проще найти её источник.
Раскрутка стека или аварийное завершение при панике
По умолчанию при панике программа начинает раскрутку стека (unwinding): Rust проходит по стеку в обратном направлении и очищает данные каждой функции, которая ему встречается. Но такой обход и очистка — большая работа. Поэтому Rust позволяет выбрать другой вариант — немедленное аварийное завершение (abort), при котором программа заканчивается без всякой очистки.
Память, которую занимала программа, тогда придётся освобождать
операционной системе. Если в вашем проекте нужно, чтобы бинарник был как
можно меньше, можно переключиться с раскрутки на аварийное завершение при
панике, добавив panic = 'abort' в соответствующие секции [profile] файла
Cargo.toml. Например, чтобы аварийно завершаться при панике в режиме
релиза, добавьте:
[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' (6018279) 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! вызывается не в нашем
коде, а в коде, который вызывается из нашего, — например, в библиотеке. Тогда в
сообщении будет файл и строка из этого чужого кода, а не строка нашего кода,
которая в итоге привела к панике.
Чтобы найти часть нашего кода, которая вызывает проблему, можно
воспользоваться трассировкой (backtrace) функций, из которых пришёл вызов
panic!. Чтобы понять, как с ней работать, рассмотрим другой пример: на этот
раз panic! вызывается не нашим кодом напрямую, а библиотекой — из-за бага в
нашем коде. В листинге 9-1 код пытается обратиться к элементу вектора по
индексу за пределами допустимых.
fn main() {
let v = vec![1, 2, 3];
v[99];
}
panic!Здесь мы пытаемся получить сотый элемент вектора (с индексом 99, ведь
нумерация начинается с нуля), а в векторе всего три элемента. В такой
ситуации 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' (6017887) 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 и получить трассировку того, что именно привело к ошибке.
Трассировка (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! и обсудим, когда стоит, а когда не стоит
обрабатывать ошибки с его помощью, в разделе «Паниковать или не
паниковать» этой главы. А дальше
посмотрим, как восстанавливаться после ошибок с помощью Result.