Введение #
Гарантии безопасности памяти Rust — это мощнейший щит от утечек памяти, висячих указателей и гонок данных (Data Races). Однако компьютерные системы низкого уровня требуют прямого взаимодействия с железом, вызова системных функций ОС и сопряжения с legacy-кодом на языке C.
Для таких задач в языке предусмотрен Unsafe Rust.
Важно понимать: ключевое слово unsafe не отключает систему типов Rust и не превращает код в C. Оно лишь даёт разработчику 5 конкретных суперспособностей для выхода за пределы строгой системы заимствований.
В этой статье мы разберем сырые указатели, Неопределенное поведение (Undefined Behavior — UB), инструмент cargo miri и связывание с С-библиотеками через FFI.
1. Пять суперспособностей Unsafe Rust #
Блок unsafe { ... } позволяет выполнять строго 5 операций, запрещенных в обычном Rust-коде:
- Разыменование сырых указателей (
*const Tи*mut T). - Вызов
unsafeфункций или методов (включая FFI C-функции). - Реализация
unsafeтипажей (например,SendиSync). - Изменение изменяемых статических переменных (
static mut). - Доступ к полям объединений (
union).
Всё остальное — проверка типов, отслеживание времён жизни и безопасность сопоставлений — продолжает работать внутри unsafe в полном объеме.
2. Сырые указатели: *const T и *mut T
#
В отличие от безопасных ссылок &T и &mut T, сырые указатели:
- Могут быть нулевыми (
null) или невалидными. - Могут быть невыровненными в памяти.
- Не гарантируют автоматической очистки памяти при выходе из области видимости.
- Игнорируют правила алиасинга (можно одновременно иметь
*const Tи*mut Tна одну ячейку памяти).
Попробуйте запустить примеры создания сырых указателей в интерактивных слайдах:
3. Неопределенное поведение (Undefined Behavior) и Miri #
Если вы пишете unsafe код, вы берёте на себя обязанность соблюдать контракты безопасности. Нарушение этих правил ведет к Undefined Behavior (UB):
- Разыменование
nullили висячего указателя. - Создание невалидного значения (например
bool, хранящий байт2). - Гонки данных при параллельной записи из разных потоков.
Для автоматической проверки вашего unsafe кода на наличие скрытых ошибок памяти используйте инструмент cargo miri test — интерпретатор синтаксического дерева Rust, который перехватывает любые попытки совершить UB на этапе тестирования.
4. Интеграция с языком C (FFI и extern "C")
#
Foreign Function Interface (FFI) позволяет вызывать функции из динамических и статических C-библиотек или предоставлять Rust-функции для вызова из C или Python.
Для объявления внешних C-сигнатур используется блок extern "C":
extern "C" {
fn abs(input: i32) -> i32;
}А для работы со строками C в модуле std::ffi предусмотрены специальный типы: CStr (заимствованная C-строка с нулевым байтом \0 на конце) и CString (владеющая C-строка).
Динамическая загрузка библиотек в Runtime (libloading)
#
Если C-библиотека не слинкована статически во время компиляции, вы можете загружать .so / .dll / .dylib файлы прямо во время выполнения программы с помощью крейта libloading:
use libloading::{Library, Symbol};
type GetIntFn = unsafe extern "C" fn() -> i32;
fn load_external_symbol() -> Result<i32, Box<dyn std::error::Error>> {
let lib = unsafe { Library::new("libget_integer.so")? };
let func: Symbol<GetIntFn> = unsafe { lib.get(b"get_my_integer")? };
let result = unsafe { func() };
Ok(result)
}Запустите примеры вызова C-функций и работы с C-строками:
Проверь свои знания! #
Пройдите короткий тест по Unsafe Rust и FFI: