Небезопасный код

Главная сила Rust — в мощных статических гарантиях правильности поведения программы во время исполнения. Но проверки безопасности очень осторожны: на самом деле, существуют безопасные программы, правильность которых компилятор доказать не в силах. Чтобы писать такие программы, нужен способ немного ослабить ограничения. Для этого в Rust есть ключевое слово unsafe. Код, использующий unsafe, ограничен меньше, чем обычный код.

Давайте рассмотрим синтаксис, а затем поговорим о семантике. unsafe используется в четырёх контекстах. Первый — это объявление того, что функция небезопасна:

fn main() { unsafe fn beregis_avtomobilya() { // страшные вещи } }
unsafe fn beregis_avtomobilya() {
    // страшные вещи
}

Например, все функции, вызываемые через FFI, должны быть помечены как небезопасные. Другое использование unsafe — это отметка небезопасного блока:

fn main() { unsafe { // страшные вещи } }
unsafe {
    // страшные вещи
}

Третье — небезопасные типажи:

fn main() { unsafe trait Scary { } }
unsafe trait Scary { }

И четвёртое — реализация (impl) таких типажей:

fn main() { unsafe trait Scary { } unsafe impl Scary for i32 {} }
unsafe impl Scary for i32 {}

Важно явно выделить код, ошибки в котором могут вызвать большие проблемы. Если программа на Rust падает с "segmentation fault", можете быть уверены — проблема в участке, помеченном как небезопасный.

Что значит "безопасный"?

В контексте Rust "безопасный" значит "не делает ничего небезопасного". Также важно знать, что некоторое поведение скорее всего нежелательно, но явно не считается небезопасным:

Rust не может предотвратить все виды проблем в программах. Код с ошибками может и будет написан на Rust. Вышеперечисленные вещи неприятны, но они не считаются именно что небезопасными.

В дополнение к этому, ниже представлен список неопределённого поведения (undefined behavior) в Rust. Избегайте этих вещей, даже когда пишете небезопасный код:

Сверхспособности небезопасного кода

В небезопасном блоке или функции, Rust разрешает три ситуации, которые обычно запрещены. Всего три. Вот они:

  1. Доступ к или изменение статической изменяемой переменной.
  2. Разыменование сырого указателя.
  3. Вызов небезопасных функций. Это самая мощная возможность.

Это всё. Важно отметить, что unsafe, например, не "выключает проверку заимствования". Объявление какого-то кода небезопасным не изменяет его семантику; небезопасность не означает принятие компилятором любого кода. Но она позволяет писать вещи, которые нарушают некоторые из правил.

Вы также встретите ключевое слово unsafe, когда будете реализовывать интерфейс к чужому коду не на Rust. Идиоматичным считается написание безопасных обёрток вокруг небезопасных библиотек.

Давайте поговорим о трёх упомянутых возможностях, доступных в небезопасном коде.

Доступ или изменение static mut

Rust позволяет пользоваться глобальным изменяемым состоянием с помощью static mut. Это может вызвать гонку по данным, и в сущности небезопасно. Подробнее смотрите раздел о static.

Разыменование сырого указателя

Сырые указатели поддерживают произвольную арифметику указетелей, и могут вызвать целый ряд проблем безопасности памяти и безопасности в целом. В каком-то смысле, возможность разыменовать произвольный указатель — одна из самых опасных вещей, которые вы можете сделать. Подробнее смотрите раздел о сырых указателях.

Вызов небезопасных функций

Эта возможность затрагивает то, откуда можно делать вызов небезопасного кода: небезопасные функции могут вызываться только из небезопасных блоков.

Мощь и полезность этой возможности сложно переоценить. Rust предоставляет некоторые intrinsic-операции компилятора в виде небезопасных функций, а некоторые небезопасные функции обходят проверки безопасности для достижения большей скорости исполнения.

В заключение, повторимся: хотя вы и можете делать в небезопасных участках почти что угодно, это не значит, что стоит это делать. Компилятор будет предполагать выполнение оговоренных инвариантов, так что будьте осторожны!