Привет, Хабр! Представляю вашему вниманию перевод статьи "What Is Rust's unsafe?" автора Nora Codes. Мне доводилось видеть много недопониманий относительно того, что значит ключевое слово unsafe для полезности и правильности языка Rust и его продвижения как "безопасного языка системного программирования". Правда намного сложнее, чем можно описать в коротком твите, к сожалению. Вот как я ее вижу. В целом, ключевое слово unsafe не выключает систему типов, которая поддерживает код на Rust корректным. Она только дает возможность использовать некоторые "суперспособности", такие как разыменование указателей. unsafe используется для реализации безопасных абстракций на основе фундаментально небезопасного мира, чтобы большая часть кода на Rust могла использовать эти абстракции и избегать небезопасного доступа к памяти. Rust гарантирует безопасность как один из своих главных принципов. Можно сказать, что это смысл существования языка. Он, однако, не предоставляет безопасность в традиционном смысле, во время выполнения программы и с использованием сборщика мусора. Вместо этого Rust использует очень продвинутую систему типов, чтобы следить за тем, когда и к каким значениям можно получать доступ. Затем компилятор статически анализирует каждую программу на Rust, чтобы убедиться в том, что она постоянно находится в корректном состоянии. Давайте возьмем для примера Python. Чистый код на Python не может повредить память. Доступ к элементам списков имеет проверки на выход за границы; ссылки, возвращаемые функциями, подсчитываются во избежание появления висячих ссылок; нет никакого способа производить произвольные арифметические операции с указателями. У этого есть два следствия. Во-первых, много типов должны быть "специальными". Например, невозможно реализовать эффективный список или словарь на чистом Python. Вместо этого интерпретатор CPython имеет их внутреннюю реализацию. Во-вторых, доступ к внешним функциям (функциям, реализованным не на Python), называемый интерфейсом внешней функции, требует использования специального модуля ctypes и нарушает гарантии безопасности языка. В каком-то смысле это значит, что все, что написано на Python, не гарантирует безопасного доступа к памяти. Rust тоже предоставляет безопасность, но вместо реализации небезопасных структур на C, он предоставляет уловку: ключевое слово unsafe. Это означает, что фундаментальные структуры данных в Rust, такие как Vec, VecDeque, BTreeMap и String, реализованы на языке Rust. Вы спросите: "Но, если Rust предоставляет уловку против его гарантий безопасности кода, и стандартная библиотека реализована с использованием этой уловки, разве не все в Rust будет считаться небезопасным?" Одним словом, уважаемый читатель, — да, именно так, как это было в Python. Давайте разберем это подробнее. Безопасность в Rust хорошо определена: мы много о ней думаем. Вкратце, безопасные программы на Rust не могут: Rust кодирует эту информацию в системе типов или используя алгебраические типы данных, такие как Option для обозначения существования/отсутствия значения и Result<T, E> для обозначения ошибки/успеха, или ссылки и их время жизни, например, &T vs &mut T для обозначения общей (неизменяемой) ссылки и эксклюзивной (изменяемой) ссылки и &'a T vs &'b T для различения ссылок, которые являются корректными в различных контекстах (такое, как правило, опускается, так как компилятор достаточно умный, чтобы сам понять это).
 Например, следующий код не будет компилироваться, так как он содержит висячую ссылку. Конкретнее, my_struct does not live enough. Иными словами, функция вернет ссылку на что-то, что уже не существует, и поэтому компилятор не сможет (и, на самом деле, даже не знает, как) скомпилировать это. Этот код делает то же самое, но он пытается обойти эту проблему, размещая значение в куче (Box — это имя базового умного указателя в Rust). Правильный код возвращает сам Box вместо ссылки на него. Это кодирует перемещение владения — ответственности за освобождение памяти — в сигнатуре функции. При взгляде на сигнатуру становится понятно, что вызывающий код ответственен за то, что произойдет с Box, и, действительно, компилятор обрабатывает это автоматически.
 Небезопасный код на Rust — это код на Rust с ключевым словом unsafe. unsafe может применяться к функции или блоку кода. Когда оно применяется к функции, это значит "эта функция требует, чтобы вызываемый код вручную обеспечивал инвариант, который обычно обеспечивается компилятором". Когда оно применяется к блоку кода, это значит "этот блок кода вручную обеспечивает инвариант, необходимый для предотвращения небезопасного доступа к памяти, и поэтому ему разрешено делать небезопасные вещи". Иными словами, unsafe у функции значит "тебе надо все проверить", а на блоке кода — "я уже все проверил". Как отмечено в книге The Rust Programming Language, код в блоке, отмеченном ключевым словом unsafe, может: Помните те примеры с висячими указателями, приведенные выше? Добавьте слово unsafe, и компилятор станет ругаться вдвое больше, потому что ему не нравится использование unsafe там, где оно не требуется. Вместо этого ключевое слово unsafe используется для реализации безопасных абстракций на основе произвольных операций над указателями. Например, тип Vec реализуется с использованием unsafe, но его безопасно использовать, так как он проверяет попытки получить доступ к элементам и не допускает переполнения. Хотя он и предоставляет операции наподобие set_len, которые могут вызвать небезопасность доступа к памяти, они помечены как unsafe. Например, мы могли бы сделать то же самое, что и в примере no_dangling_reference, но с беспричинным использованием unsafe: Заметьте отсутствие слова unsafe. Создание указателей абсолютно безопасно. Как было написано, это риск возникновения утечки памяти, но ничего более, а утечки памяти безопасны. Вызов этой функции тоже безопасен. unsafe требуется только тогда, когда что-то пытается разыменовать указатель. Как дополнительный бонус разыменование позволит автоматически освободить выделенную память. После оптимизации этот код эквивалентен простому возвращению Box. Box — безопасная абстракция на основе указателей, потому что она предотвращает распространение указателей повсюду. Например, следующая версия main приведет к двойному освобождению памяти (double-free). Безопасная абстракция — это такая абстракция, которая использует систему типов для предоставления API, который не может быть использован для нарушения гарантий безопасности, которые были упомянуты выше. Box безопаснее *mut T, так как он не может привести к двойному освобождению памяти, проиллюстрированному выше. Другой пример — тип Rc в Rust. Это указатель с подсчетом ссылок — не допускающая изменение ссылка на данные, расположенные в куче. Так как она допускает множественный одновременный доступ к одной области памяти, она должна предотвращать изменение для того, чтобы считаться безопасной. В дополнение к этому она не потокобезопасна. Если вам нужна потокобезопасность, вам придется использовать тип Arc (Atomic Reference Counting), который имеет штраф к производительности из-за использования atomic значений для подсчета ссылок и предотвращения возможных гонок данных в многопоточных средах. Компилятор не позволит вам использовать Rc там, где вы должны использовать Arc, потому что создатели типа Rc не отметили его как потокобезопасный. Если бы они сделали это, это было бы необоснованно: ложное обещание безопасности. Небезопасный Rust необходим всегда, когда необходимо произвести операцию, нарушающую одно из тех двух правил, описанных выше. Например, в двусвязном списке отсутствие изменяемых ссылок на одни и те же данные (у следующего элемента и предыдущего элемента) полностью лишает его пользы. С unsafe имплементатор двусвязного списка может написать код, используя указатели *mut Node и затем инкапсулировать это в безопасную абстракцию.
 Другой пример — работа со встраиваемыми системами. Часто микроконтроллеры используют набор регистров, чьи значения определяются физическим состоянием устройства. Мир не может остановиться, пока вы берете &mut u8 с такого регистра, поэтому для работы с крэйтами поддержки устройства необходим unsafe. Как правило, такие крэйты инкапсулируют состояние в прозрачных безопасных обертках, которые по возможности копируют данные, или же используют другие техники, обеспечивающие гарантии компилятора. Иногда необходимо провести операцию, которая может привести к одновременному чтению и записи, или небезопасному доступу к памяти, и именно здесь нужен unsafe. Но до тех пор, пока есть возможность убедиться в поддержании безопасных инвариантов перед тем, как пользователь безопасного (то есть не отмеченного unsafe) кода коснется чего-то, все в порядке. Мы приходим к сделанному раньше утверждению — да, полезность кода на Rust основана на небезопасном коде. Несмотря на то, что это сделано несколько иначе, чем небезопасная имплементация основных структур данных в Python, реализация Vec, Hashmap и т. д. должна использовать манипуляции указателями в какой-либо степени. Мы говорим, что Rust безопасен, с фундаментальным допущением, что небезопасный код, который мы используем через наши зависимости либо от стандартной библиотеки, либо от кода других библиотек, корректно написан и инкапсулирован. Фундаментальное преимущество Rust заключается в том, что небезопасный код загнан в небезопасные блоки, которые должны быть тщательно проверены их авторами. В Python бремя проверки безопасности манипуляций с памятью лежит только на разработчиках интерпретаторов и пользователях интерфейсов внешних функций. В C это бремя лежит на каждом программисте. В Rust оно лежит на пользователях ключевого слова unsafe. Это очевидно, так как внутри такого кода инварианты должны поддерживаться вручную, и поэтому необходимо стремиться к наименьшему объему такого кода в библиотеке или коде приложения. Небезопасность обнаруживается, выделяется и обозначается. Поэтому, если в вашем коде на Rust возникают segfaults, то вы нашли либо ошибку в компиляторе, либо ошибку в нескольких строках вашего небезопасного кода. Это неидеальная система, но, если вам одновременно нужны скорость, безопасность и многопоточность, то это единственный возможный вариант.