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

Невосстанавливаемые ошибки с panic!

Иногда в вашем коде происходят плохие вещи, и вы ничего не можете с этим поделать. В таких случаях Rust предоставляет макрос panic!. На практике есть два способа вызвать панику: выполнить действие, которое приводит к панике (например, доступ к элементу массива за его пределами), или явно вызвать макрос panic!. В обоих случаях мы вызываем панику в нашей программе. По умолчанию такие паники выводят сообщение об ошибке, выполняют разворачивание стека (unwinding), очищают стек и завершают программу. С помощью переменной окружения вы также можете заставить Rust отображать трассировку стека при панике, чтобы упростить поиск источника проблемы.

Разворачивание стека или аварийное завершение в ответ на панику

По умолчанию, когда происходит паника, программа начинает разворачивать стек (unwinding), что означает, что Rust проходит вверх по стеку и очищает данные из каждой встреченной функции. Однако такой процесс требует много работы. Поэтому Rust позволяет выбрать альтернативу — немедленное аварийное завершение (aborting), которое завершает программу без очистки.

Память, которую использовала программа, затем будет очищена операционной системой. Если в вашем проекте необходимо сделать итоговый бинарный файл как можно меньше, вы можете переключиться с разворачивания на аварийное завершение при панике, добавив panic = 'abort' в соответствующие секции [profile] в вашем файле Cargo.toml. Например, если вы хотите аварийно завершать программу при панике в режиме выпуска (release), добавьте это:

[profile.release]
panic = 'abort'

Давайте попробуем вызвать panic! в простой программе:

Filename: src/main.rs
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!, а не к строке нашего кода, которая в конечном итоге привела к этому вызову.

Мы можем использовать трассировку стека (backtrace) функций, из которых произошел вызов panic!, чтобы выяснить, какая часть нашего_code вызывает проблему. Чтобы понять, как использовать трассировку стека при panic!, давайте рассмотрим другой пример и посмотрим, как это выглядит, когда вызов panic! исходит из крейта из-за ошибки в нашем коде, а не из нашего прямого вызова макроса. В листинге 9-1 есть код, который пытается получить доступ к элементу вектора по индексу, выходящему за пределы допустимых индексов.

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

    v[99];
}
Listing 9-1: Попытка доступа к элементу за пределами вектора, что приведет к вызову panic!

Здесь мы пытаемся получить доступ к 100-му элементу нашего вектора (который имеет индекс 99, так как индексация начинается с нуля), но вектор содержит только три элемента. В такой ситуации Rust вызовет панику. Использование [] предполагает возврат элемента, но если передать недопустимый индекс, не существует элемента, который Rust мог бы вернуть и который был бы корректным.

В C попытка чтения за пределами структуры данных приводит к неопределённому поведению (undefined behavior). Вы можете получить любые данные, находящиеся в памяти, которые соответствуют этому элементу в структуре, даже если память не принадлежит этой структуре. Это называется переполнением буфера при чтении (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

Эта ошибка указывает на строку 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.
Listing 9-2: Трассировка стека, сгенерированная вызовом panic!, отображаемая при установке переменной окружения RUST_BACKTRACE

Это очень много вывода! Точный вывод, который вы увидите, может отличаться в зависимости от вашей операционной системы и версии Rust. Чтобы получать трассировки стека с такой информацией, должны быть включены символы отладки (debug symbols). Символы отладки включены по умолчанию при использовании cargo build или cargo run без флага --release, как мы и сделали.

В выводе в листинге 9-2 строка 6 трассировки стека указывает на строку в нашем проекте, которая вызывает проблему: строку 4 файла src/main.rs. Если мы не хотим, чтобы наша программа паниковала, мы должны начать расследование в месте, на которое указывает первая строка, упоминающая файл, который мы написали. В листинге 9-1, где мы намеренно написали код, который вызовет панику, способ исправить панику — не запрашивать элемент за пределами диапазона индексов вектора. Когда ваш код паникует в будущем, вам нужно будет выяснить, какое действие выполняет код с какими значениями, что вызывает панику, и что код должен делать вместо этого.

Мы вернемся к panic! и к тому, когда мы должны и не должны использовать panic! для обработки условий ошибок, в разделе «panic! или не panic!» позже в этой главе. Далее мы посмотрим, как восстанавливаться после ошибки с помощью Result.