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 предоставляет механизм, который позволяет функции не возвращать управление обычным образом, а вместо этого “паниковать”. Это реакция на условие ошибки, которое обычно считается невосстановимым в контексте, где эта ошибка возникла.

Некоторые языковые конструкции, такие как индексация массива за его пределами, паникуют автоматически.

Также существуют языковые возможности, которые предоставляют определенный уровень контроля над поведением паники:

Note

Стандартная библиотека предоставляет возможность явно вызвать панику с помощью макроса panic!.

Атрибут panic_handler

Атрибут panic_handler может быть применен к функции для определения поведения при панике.

Атрибут panic_handler может быть применен только к функции с сигнатурой fn(&PanicInfo) -> !.

Note

Структура PanicInfo содержит информацию о месте возникновения паники.

В графе зависимостей должна присутствовать ровно одна функция panic_handler.

Ниже показана функция panic_handler, которая записывает сообщение о панике и затем останавливает поток.

#![no_std]

use core::fmt::{self, Write};
use core::panic::PanicInfo;

struct Sink {
    // ..
   _0: (),
}

impl Sink {
    fn new() -> Sink { Sink { _0: () }}
}

impl fmt::Write for Sink {
    fn write_str(&mut self, _: &str) -> fmt::Result { Ok(()) }
}

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    let mut sink = Sink::new();

    // записывает "panicked at '$reason', src/main.rs:27:4" в некоторый `sink`
    let _ = writeln!(sink, "{}", info);

    loop {}
}

Стандартное поведение

std предоставляет два различных обработчика паники:

  • unwind — раскручивает стек и потенциально является восстанавливаемым.
  • abort — аварийно завершает процесс и является невосстанавливаемым.

Не все целевые платформы могут предоставлять обработчик unwind.

Note

Обработчик паники, используемый при линковке с std, может быть задан с помощью флага командной строки -C panic. По умолчанию для большинства целей используется unwind.

Поведение паники в стандартной библиотеке может быть изменено во время выполнения с помощью функции std::panic::set_hook.

Линковка бинарного файла no_std, динамической библиотеки (dylib, cdylib) или статической библиотеки (staticlib) потребует указания собственного обработчика паники.

Стратегия паники (Panic strategy)

Стратегия паники определяет тип поведения при панике, для поддержки которого собирается крейт.

Note

Стратегия паники может быть выбрана в rustc с помощью флага командной строки -C panic.

При создании бинарного файла, динамической библиотеки (dylib, cdylib) или статической библиотеки (staticlib) и линковке с std, флаг -C panic также влияет на то, какой обработчик паники будет использован.

Note

При компиляции кода со стратегией паники abort оптимизатор может предполагать, что раскрутка стека через фреймы Rust невозможна, что может привести как к уменьшению размера кода, так и к повышению скорости выполнения.

Note

См. link.unwinding об ограничениях на линковку крейтов с разными стратегиями паники. Из этого следует, что крейты, собранные со стратегией unwind, могут использовать обработчик паники abort, но стратегия abort не может использовать обработчик паники unwind.

Раскрутка стека (Unwinding)

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

Когда возникает паника, обработчик unwind “раскручивает” фреймы Rust, подобно тому как throw в C++ раскручивает фреймы C++, до тех пор пока паника не достигнет точки восстановления (например, на границе потока). Это означает, что пока паника проходит через фреймы Rust, у живых объектов в этих фреймах, которые реализуют Drop, будут вызваны их методы drop. Таким образом, когда нормальное выполнение возобновится, более недоступные объекты будут “очищены” так, как если бы они вышли из области видимости обычным образом.

Note

Пока сохраняется эта гарантия очистки ресурсов, “раскрутка” может быть реализована без фактического использования механизма, используемого C++ для целевой платформы.

Note

Стандартная библиотека предоставляет два механизма для восстановления после паники: std::panic::catch_unwind (который позволяет восстановиться внутри паникующего потока) и std::thread::spawn (который автоматически настраивает восстановление после паники для порожденного потока, чтобы другие потоки могли продолжать работу).

Раскрутка через границы FFI

Возможна раскрутка стека через границы FFI с использованием соответствующего объявления ABI. Хотя это полезно в определенных случаях, это создает уникальные возможности для неопределенного поведения, особенно когда задействованы среды выполнения нескольких языков.

Раскрутка стека с неправильным ABI является неопределенным поведением:

  • Вызов раскрутки в код Rust из внешней функции, которая была вызвана через объявление функции или указатель, объявленный с непредусматривающим-раскрутку ABI, таким как "C", "system" и т.д. (Например, это происходит, когда такая функция, написанная на C++, генерирует исключение, которое не перехватывается и передается в Rust.)
  • Вызов Rust extern функции, которая производит раскрутку (с extern "C-unwind" или другим ABI, разрешающим раскрутку), из кода, который не поддерживает раскрутку, такого как код, скомпилированный с помощью GCC или Clang с использованием -fno-exceptions.

Перехват операции раскрутки извне (такой как исключение C++) с помощью std::panic::catch_unwind, std::thread::JoinHandle::join или позволение ей распространиться за пределы функции Rust main() или корня потока приведет к одному из двух видов поведения, и не указано, какое именно произойдет:

  • Процесс аварийно завершается.
  • Функция возвращает Result::Err, содержащий непрозрачный тип.

Note

Код Rust, скомпилированный или слинкованный с другим экземпляром стандартной библиотеки Rust, считается “исключением извне” для целей этой гарантии. Таким образом, библиотека, которая использует panic! и слинкована с одной версией стандартной библиотеки Rust, вызванная из приложения, которое использует другую версию стандартной библиотеки, может привести к аварийному завершению всего приложения, даже если библиотека используется только внутри дочернего потока.

В настоящее время нет гарантий относительно поведения, которое происходит, когда внешняя среда выполнения пытается обработать или повторно сгенерировать полезную нагрузку (payload) Rust panic. Другими словами, раскрутка, исходящая из среды выполнения Rust, должна либо привести к завершению процесса, либо быть перехвачена той же самой средой выполнения.