Распространённые предусловия безопасности
- Псевдонимы и изменяемость
- Выравнивание
- Доступ к массиву в границах
- Инициализация
- Время жизни ссылок
- Происхождение указателя (pointer provenance)
- Корректность значения (validity)
- Память
Не тратьте много времени на объяснение каждого предусловия: подробности мы разберём в ходе курса. Цель — показать, что их несколько.
«Неполный список, но это несколько основных предусловий безопасности, которые помогут нам задуматься».
- Корректность значения (validity). Значения должны быть корректными значениями того типа, который они представляют. Ссылки Rust не могут быть null. Создание такой ссылки через
unsafeприводит к - Выравнивание (alignment). Ссылки на значения должны быть правильно выровнены, что означает, что
- Псевдонимы (aliasing). Весь код Rust должен соблюдать правила заимствования Rust. Если вы вручную создаёте изменяемые ссылки (
&mut T) из указателей, то можете создать только одну - Инициализация. Все экземпляры типов Rust должны быть полностью инициализированы. Чтобы создать значение из сырой памяти, нужно убедиться, что мы записали
- Происхождение указателя (pointer provenance). Происхождение указателя важно. Приведение
usizeк сырому указателю больше не допускается. - Времена жизни. Ссылки не должны переживать то, на что они указывают.
Некоторые условия даже тоньше, чем кажутся на первый взгляд.
Рассмотрим «доступ к массиву в границах». Для нарушения программы не обязательно читать ячейку памяти, то есть разыменовывать указатель. Создание ссылки за границами массива уже нарушает предположения компилятора и приводит к непредсказуемому поведению.
Rust сообщает LLVM использовать его предположение getelementptr inbounds. Это предположение приводит к тому, что последующие проходы оптимизации в компиляторе ведут себя неправильно, поскольку доступ к памяти за границами не может происходить.
Необязательно: откройте песочницу, в которой показан приведённый ниже код. Объясните, что это, по сути, C-функция, записанная синтаксисом Rust, которая берёт элементы из массива. Сгенерируйте LLVM IR кнопкой Show LLVM IR. Выделите getelementptr inbounds i32, ptr %array, i64 %offset.
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
#[unsafe(no_mangle)]
pub unsafe fn get(array: *const i32, offset: isize) -> i32 {
unsafe { *array.offset(offset) }
}
Ожидаемый вывод (строка для выделения начинается с `%_3):
define noundef i32 @get(ptr noundef readonly captures(none) %array, i64 noundef %offset) unnamed_addr #0 {
start:
%_3 = getelementptr inbounds i32, ptr %array, i64 %offset
%_0 = load i32, ptr %_3, align 4, !noundef !3
ret i32 %_0
}
Границы: вы верно заметили, что создание указателя за пределами массива (сверх правила «один за концом») — это неопределённое поведение даже без разыменования, из-за предположений LLVM о inbounds.