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

Поведение, считающееся неопределенным

Код Rust является некорректным, если он демонстрирует любое из поведений в следующем списке. Это включает код внутри unsafe блоков и unsafe функций. unsafe только означает, что избежание неопределенного поведения лежит на программисте; это не меняет ничего в том факте, что программы Rust никогда не должны вызывать неопределенное поведение.

Это ответственность программиста при написании unsafe кода обеспечить, чтобы любой безопасный код, взаимодействующий с unsafe кодом, не мог вызвать эти поведения. unsafe код, который удовлетворяет этому свойству для любого безопасного клиента, называется корректным (sound); если unsafe код может быть неправильно использован безопасным кодом для демонстрации неопределенного поведения, он некорректный (unsound).

Warning

Следующий список не является исчерпывающим; он может расти или сокращаться. Не существует формальной модели семантики Rust для того, что разрешено, а что нет в небезопасном коде, поэтому может быть больше поведений, считающихся небезопасными. Мы также оставляем за собой право сделать некоторое поведение в этом списке определенным в будущем. Другими словами, этот список не говорит, что что-то определенно всегда будет неопределенным во всех будущих версиях Rust (но мы можем сделать такие обязательства для некоторых элементов списка в будущем).

Пожалуйста, прочитайте Rustonomicon перед написанием небезопасного кода.

  • Гонки данных (Data races).
  • Нарушение правил псевдонимов указателей. Точные правила псевдонимов еще не определены, но вот общий принцип: &T должен указывать на память, которая не изменяется, пока они живы (за исключением данных внутри UnsafeCell<U>), и &mut T должен указывать на память, которая не читается и не записывается любым указателем, не производным от ссылки, и на которую не указывает никакая другая ссылка, пока они живы. Box<T> обрабатывается аналогично &'static mut T для целей этих правил. Точная продолжительность жизни не указана, но существуют некоторые границы:

    • Для ссылок продолжительность жизни ограничена сверху синтаксическим временем жизни, назначенным проверщиком заимствований; она не может жить дольше, чем это время жизни.
    • Каждый раз, когда ссылка или бокс разыменовывается или перезаимствуется, они считаются живыми.
    • Каждый раз, когда ссылка или бокс передаются в функцию или возвращаются из функции, они считаются живыми.
    • Когда ссылка (но не Box!) передается в функцию, она жива по крайней мере так долго, как этот вызов функции, снова за исключением случая, когда &T содержит UnsafeCell<U>.

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

  • Изменение неизменяемых байтов. Все байты, доступные через const-промотированное выражение, являются неизменяемыми, а также байты, доступные через заимствования в инициализаторах static и const, которые были расширены по времени жизни до 'static. Байты, принадлежащие неизменяемой привязке или неизменяемому static, являются неизменяемыми, если эти байты не являются частью UnsafeCell<U>.

    Более того, байты, указываемые разделяемой ссылкой, включая транзитивно через другие ссылки (как разделяемые, так и изменяемые) и Boxы, являются неизменяемыми; транзитивность включает те ссылки, которые хранятся в полях составных типов.

    Изменением является любая запись более чем 0 байтов, которая перекрывается с любыми из соответствующих байтов (даже если эта запись не изменяет содержимое памяти).

  • Вызов неопределенного поведения через компиляторные интринсики.
  • Выполнение кода, скомпилированного с особенностями платформы, которые текущая платформа не поддерживает (см. target_feature), за исключением случаев, когда платформа явно документирует, что это безопасно.
  • Вызов функции с неправильным ABI вызова или раскрутка стека за фрейм, который не позволяет раскрутку (например, вызовом функции "C-unwind", импортированной или преобразованной как функция или указатель на функцию "C").
  • Создание недопустимого значения. “Создание” значения происходит всякий раз, когда значение присваивается или читается из места, передается в функцию/примитивную операцию или возвращается из функции/примитивной операции.
  • Неправильное использование встроенного ассемблера. Для более подробной информации обратитесь к правилам для следования при написании кода, который использует встроенный ассемблер.
  • В контексте const: преобразование или иная реинтерпретация указателя (ссылки, сырого указателя или указателя на функцию) в некоторое выделение как в не-указательный тип (такой как целые числа). ‘Реинтерпретация’ относится к загрузке значения указателя как целочисленного типа без приведения, например, путем приведения сырых указателей или использования объединения (union).
  • Нарушение предположений среды выполнения Rust. Большинство предположений среды выполнения Rust в настоящее время не документированы явно.
    • Для предположений, специфически связанных с раскруткой стека, см. документацию по панике.
    • Среда выполнения предполагает, что фрейм стека Rust не освобождается без выполнения деструкторов для локальных переменных, принадлежащих фрейму стека. Это предположение может быть нарушено функциями C, такими как longjmp.

Note

Неопределенное поведение влияет на всю программу. Например, вызов функции в C, которая демонстрирует неопределенное поведение C, означает, что вся ваша программа содержит неопределенное поведение, которое также может повлиять на код Rust. И наоборот, неопределенное поведение в Rust может вызвать неблагоприятные эффекты на код, выполняемый любыми вызовами FFI к другим языкам.

Указываемые байты

Диапазон байтов, на которые “указывает” указатель или ссылка, определяется значением указателя и размером типа указываемого объекта (используя size_of_val).

Места, основанные на невыровненных указателях

Говорят, что место “основано на невыровненном указателе”, если последняя * проекция во время вычисления места была выполнена на указателе, который не был выровнен для его типа. (Если в выражении места нет * проекции, то это доступ к полю локальной переменной или static, и rustc гарантирует правильное выравнивание. Если есть несколько * проекций, то каждая из них влечет загрузку самого указателя для разыменования из памяти, и каждая из этих загрузок подвержена ограничению выравнивания. Обратите внимание, что некоторые * проекции могут быть опущены в поверхностном синтаксисе Rust из-за автоматического разыменования; мы рассматриваем полностью развернутое выражение места здесь.)

Например, если ptr имеет тип *const S, где S имеет выравнивание 8, то ptr должен быть выровнен по 8, иначе (*ptr).f “основано на невыровненном указателе”. Это верно, даже если тип поля f - u8 (т.е. тип с выравниванием 1). Другими словами, требование выравнивания происходит от типа указателя, который был разыменован, а не от типа поля, к которому осуществляется доступ.

Обратите внимание, что место, основанное на невыровненном указателе, приводит к Неопределенному Поведению только когда оно загружается или сохраняется.

&raw const/&raw mut на таком месте разрешены.

&/&mut на месте требует выравнивания типа поля (или иначе программа будет “создавать недопустимое значение”), что обычно является менее строгим требованием, чем быть основанным на выровненном указателе.

Взятие ссылки приведет к ошибке компилятора в случаях, когда тип поля может быть более выровненным, чем тип, который его содержит, т.е. repr(packed). Это означает, что быть основанным на выровненном указателе всегда достаточно, чтобы гарантировать, что новая ссылка выровнена, но это не всегда необходимо.

Висячие указатели

Ссылка/указатель является “висячим (dangling)”, если не все байты, которые она указывает, являются частью того же живого выделения (так, в частности, они все должны быть частью какого-то выделения).

Если размер равен 0, то указатель тривиально никогда не является “висячим” (даже если это нулевой указатель).

Обратите внимание, что типы с динамическим размером (такие как срезы и строки) указывают на их весь диапазон, поэтому важно, чтобы метаданные длины никогда не были слишком большими.

В частности, динамический размер значения Rust (как определено size_of_val) никогда не должен превышать isize::MAX, поскольку невозможно, чтобы одно выделение было больше isize::MAX.

Недопустимые значения

Компилятор Rust предполагает, что все значения, произведенные во время выполнения программы, “допустимы”, и создание недопустимого значения, следовательно, является немедленным НП.

Является ли значение допустимым, зависит от типа:

  • Значение bool должно быть false (0) или true (1).
  • Значение указателя на функцию fn должно быть ненулевым.
  • Значение char не должно быть суррогатом (т.е. не должно быть в диапазоне 0xD800..=0xDFFF) и должно быть равно или меньше char::MAX.
  • Значение ! никогда не должно существовать.
  • Целое число (i*/u*), значение с плавающей точкой (f*) или сырой указатель должны быть инициализированы, т.е. не должны быть получены из неинициализированной памяти.
  • Значение str обрабатывается как [u8], т.е. оно должно быть инициализировано.
  • enum должен иметь допустимый дискриминант, и все поля варианта, указанного этим дискриминантом, должны быть допустимыми для их соответствующих типов.
  • struct, кортеж и массив требуют, чтобы все поля/элементы были допустимыми для их соответствующих типов.
  • Для union точные требования допустимости еще не решены. Очевидно, все значения, которые могут быть созданы entirely в безопасном коде, допустимы. Если объединение имеет поле с нулевым размером, то каждое возможное значение допустимо. Дальнейшие детали все еще обсуждаются.
  • Ссылка или Box<T> должны быть выровнены и ненулевыми, они не могут быть висячими, и они должны указывать на допустимое значение (в случае типов с динамическим размером, используя фактический динамический тип указываемого объекта, определенный метаданными). Обратите внимание, что последний пункт (о указании на допустимое значение) остается предметом некоторых дискуссий.
  • Метаданные широкой ссылки, Box<T> или сырого указателя должны соответствовать типу неразмерного хвоста:
    • Метаданные dyn Trait должны быть указателем на сгенерированную компилятором таблицу виртуальных функций для Trait. (Для сырых указателей это требование остается предметом некоторых дискуссий.)
    • Метаданные среза ([T]) должны быть допустимым usize. Кроме того, для широких ссылок и Box<T> метаданные среза недопустимы, если они делают общий размер указываемого значения больше isize::MAX.
  • Если тип имеет пользовательский диапазон допустимых значений, то допустимое значение должно быть в этом диапазоне. В стандартной библиотеке это затрагивает NonNull<T> и NonZero<T>.

    Note

    rustc достигает этого с помощью нестабильных атрибутов rustc_layout_scalar_valid_range_*.

Примечание: Неинициализированная память также неявно недопустима для любого типа, который имеет ограниченный набор допустимых значений. Другими словами, единственные случаи, в которых чтение неинициализированной памяти разрешено, - это внутри union и в “заполнении (padding)” (промежутках между полями типа).