Размотка

У Rust многоуровневая схема перехвата ошибок:

  • Если чего-то разумно не может быть, используется Option.
  • Если что-то идёт не так и может быть разумно перехвачено, используется Result.
  • Если что-то идёт не так и не может быть разумно перехвачено, поток паникует.
  • Если что-то катастрофическое случается, программа закрывается с ошибкой.

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

По состоянию на 1.0, у Rust есть два мнения, когда дело доходит до паники. Давным-давно Rust был очень похож на Erlang. Как и у Erlang, у Rust были легковесные потоки, и они должны были убивать себя с паникой, когда переходили в неприемлемое состояние. В отличие от исключений в Java или C++, панику нельзя было поймать в любое время. Её мог поймать только владелец потока в определённом месте перехвата или и этот поток начинал паниковать.

Размотка была очень важна в этом рассказе, потому что не вызов деструкторов позволял утекать памяти и другим ресурсам системы. Из-за того, что ожидалось, что потоки будут умирать во время нормального выполнения, Rust становился очень слабым при работе с долго-живущими системами!

Rust, каким мы его знаем сейчас, вырос из стиля программирования в виде создания все меньших-и-меньших абстракций. Легковесные потоки были убиты и заменены на тяжеловесные потоки ОС. Однако, на стабильном Rust 1.0 паники могут перехватываться только родительским потоком. Это означает, что поимка паники требует размотки целого потока ОС! Это, к сожалению, идёт в разрез с философией Rust - использование абстракций нулевой стоимости.

Существует нестабильное API, называемое catch_panic, которое позволяет ловить панику, не порождая поток. По-прежнему, мы просим вас пользоваться им умеренно. В частности, текущая реализация размотки в Rust сильно оптимизирована под "невыполняющие размотку" случаи. Если программа не выполняет размотку, цена ожидания размотки является нулевой во время исполнения. Как следствие, текущая версия размотки является более дорогостоящей, чем, например, в Java. Не стройте программы, использующие размотку в обычных ситуациях. В идеале вы должны вызывать панику только в случае программных ошибок или огромных проблем.

Стратегия размотки в Rust не заточена под полную совместимость с размоткой в других языках. Поэтому размотка в Rust из других языков или наоборот является Неопределённым Поведением. Вы должны ловить абсолютно все паники на границе FFI! Что конкретно вы будете делать, зависит от вас, но что-то делать надо обязательно. Если вы ошибётесь с этим, лучшее, что произойдёт, ваше приложение сломается и сгорит. Худшее - оно не сломается и не сгорит, а продолжит работать в полностью расколошмаченном состоянии.