Главная сила Rust — в мощных статических гарантиях правильности поведения
программы во время исполнения. Но проверки безопасности очень осторожны: на
самом деле, существуют безопасные программы, правильность которых компилятор
доказать не в силах. Чтобы писать такие программы, нужен способ немного ослабить
ограничения. Для этого в Rust есть ключевое слово unsafe
. Код, использующий
unsafe
, ограничен меньше, чем обычный код.
Давайте рассмотрим синтаксис, а затем поговорим о семантике. unsafe
используется в четырёх контекстах. Первый — это объявление того, что функция
небезопасна:
unsafe fn beregis_avtomobilya() { // страшные вещи }
Например, все функции, вызываемые через FFI, должны быть помечены как
небезопасные. Другое использование unsafe
— это отметка небезопасного блока:
unsafe { // страшные вещи }
Третье — небезопасные типажи:
fn main() { unsafe trait Scary { } }unsafe trait Scary { }
И четвёртое — реализация (impl
) таких типажей:
unsafe impl Scary for i32 {}
Важно явно выделить код, ошибки в котором могут вызвать большие проблемы. Если программа на Rust падает с "segmentation fault", можете быть уверены — проблема в участке, помеченном как небезопасный.
В контексте Rust "безопасный" значит "не делает ничего небезопасного". Также важно знать, что некоторое поведение скорее всего нежелательно, но явно не считается небезопасным:
Rust не может предотвратить все виды проблем в программах. Код с ошибками может и будет написан на Rust. Вышеперечисленные вещи неприятны, но они не считаются именно что небезопасными.
В дополнение к этому, ниже представлен список неопределённого поведения (undefined behavior) в Rust. Избегайте этих вещей, даже когда пишете небезопасный код:
&mut T
и &T
следуют модели LLVM noalias, кроме случаев, когда
&T
содержит UnsafeCell<U>
. Небезопасный код не должен нарушать эти
гарантии совпадения указателей.UnsafeCell<U>
std::ptr::offset
(offset
intrinsic), кроме разрешённого случая "один байт за концом объекта".std::ptr::copy_nonoverlapping_memory
(intrinsic-операции
memcpy32
/memcpy64
) с пересекающимися буферамиfalse
(0) или true
(1)char
или значение char
, превыщающее char::MAX
str
В небезопасном блоке или функции, Rust разрешает три ситуации, которые обычно запрещены. Всего три. Вот они:
Это всё. Важно отметить, что unsafe
, например, не "выключает проверку
заимствования". Объявление какого-то кода небезопасным не изменяет его
семантику; небезопасность не означает принятие компилятором любого кода. Но она
позволяет писать вещи, которые нарушают некоторые из правил.
Вы также встретите ключевое слово unsafe
, когда будете реализовывать интерфейс
к чужому коду не на Rust. Идиоматичным считается написание безопасных обёрток
вокруг небезопасных библиотек.
Давайте поговорим о трёх упомянутых возможностях, доступных в небезопасном коде.
static mut
Rust позволяет пользоваться глобальным изменяемым состоянием с помощью static mut
. Это может вызвать гонку по данным, и в сущности небезопасно. Подробнее
смотрите раздел о static.
Сырые указатели поддерживают произвольную арифметику указетелей, и могут вызвать целый ряд проблем безопасности памяти и безопасности в целом. В каком-то смысле, возможность разыменовать произвольный указатель — одна из самых опасных вещей, которые вы можете сделать. Подробнее смотрите раздел о сырых указателях.
Эта возможность затрагивает то, откуда можно делать вызов небезопасного кода: небезопасные функции могут вызываться только из небезопасных блоков.
Мощь и полезность этой возможности сложно переоценить. Rust предоставляет некоторые intrinsic-операции компилятора в виде небезопасных функций, а некоторые небезопасные функции обходят проверки безопасности для достижения большей скорости исполнения.
В заключение, повторимся: хотя вы и можете делать в небезопасных участках почти что угодно, это не значит, что стоит это делать. Компилятор будет предполагать выполнение оговоренных инвариантов, так что будьте осторожны!