Перейти к основному содержимому
  1. Rust/

Unsafe Rust и FFI: работа с сырыми указателями, Undefined Behavior и интеграция с C

993 слова·5 минут· loading · loading · · ·Rust-middle Черновик
О Rust - Эта статья часть цикла.
Статей прочитано 0/58
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
38 Unsafe Rust и FFI: работа с сырыми указателями, Undefined Behavior и интеграция с C (текущая)
Не прочитана

Введение
#

Гарантии безопасности памяти Rust — это мощнейший щит от утечек памяти, висячих указателей и гонок данных (Data Races). Однако компьютерные системы низкого уровня требуют прямого взаимодействия с железом, вызова системных функций ОС и сопряжения с legacy-кодом на языке C.

Для таких задач в языке предусмотрен Unsafe Rust.

Важно понимать: ключевое слово unsafe не отключает систему типов Rust и не превращает код в C. Оно лишь даёт разработчику 5 конкретных суперспособностей для выхода за пределы строгой системы заимствований.

В этой статье мы разберем сырые указатели, Неопределенное поведение (Undefined Behavior — UB), инструмент cargo miri и связывание с С-библиотеками через FFI.


1. Пять суперспособностей Unsafe Rust
#

Блок unsafe { ... } позволяет выполнять строго 5 операций, запрещенных в обычном Rust-коде:

  1. Разыменование сырых указателей (*const T и *mut T).
  2. Вызов unsafe функций или методов (включая FFI C-функции).
  3. Реализация unsafe типажей (например, Send и Sync).
  4. Изменение изменяемых статических переменных (static mut).
  5. Доступ к полям объединений (union).

Всё остальное — проверка типов, отслеживание времён жизни и безопасность сопоставлений — продолжает работать внутри unsafe в полном объеме.


2. Сырые указатели: *const T и *mut T
#

В отличие от безопасных ссылок &T и &mut T, сырые указатели:

  • Могут быть нулевыми (null) или невалидными.
  • Могут быть невыровненными в памяти.
  • Не гарантируют автоматической очистки памяти при выходе из области видимости.
  • Игнорируют правила алиасинга (можно одновременно иметь *const T и *mut T на одну ячейку памяти).

Попробуйте запустить примеры создания сырых указателей в интерактивных слайдах:

Сырые указатели и Безопасные абстракции
Шаг 1/2
 1// Шаг 1: Создание и разыменование сырых указателей (*const T и *mut T)
 2fn main() {
 3    let mut num: i32 = 42;
 4
 5    // Создание сырых указателей БЕЗОПАСНО и не требует блоков unsafe:
 6    let ptr_const: *const i32 = &num as *const i32;
 7    let ptr_mut: *mut i32 = &mut num as *mut i32;
 8
 9    println!("Адрес памяти (const ptr): {:?}", ptr_const);
10    println!("Адрес памяти (mut ptr):   {:?}", ptr_mut);
11
12    // Разыменование и изменение значений требует небезопасного блока unsafe:
13    unsafe {
14        *ptr_mut = 100;
15        println!("Новое значение через *ptr_mut: {}", *ptr_const);
16    }
17}
18

1. Работа с сырыми указателями

  • В отличие от обычных ссылок &T, сырые указатели могут быть нулевыми (null), невыровненными и игнорируют правила Borrow Checker.
  • Создавать указатели можно в безопасном коде, но их разыменование (*ptr) строго требует блока unsafe.

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-строками:

FFI вызовы и мост CStr / CString
Шаг 1/2
 1use std::os::raw::c_char;
 2
 3// Шаг 1: Объявление внешних C-функций через unsafe extern "C"
 4unsafe extern "C" {
 5    fn abs(input: i32) -> i32;
 6}
 7
 8fn main() {
 9    // Вызов функции abs из системной библиотеки C (libc)
10    let negative_number = -42;
11    let absolute_val = unsafe { abs(negative_number) };
12
13    println!("Результат работы C-функции abs({negative_number}): {absolute_val}");
14}
15

1. Вызов C-функций через unsafe extern “C”

  • Блок unsafe extern "C" объявляет имена и сигнатуры функций сторонней C-библиотеки.
  • Любой вызов внешней FFI-функции считается компилятором Rust небезопасным (unsafe), так как Rust не может проверить контракт C-кода.

Проверь свои знания!
#

Пройдите короткий тест по Unsafe Rust и FFI:

Статья прочитана
Пожалуйста, оцените насколько статья была вам полезна и понятна
Цикл статей
О Rust - Эта статья часть цикла.
Статей прочитано 0/58
0%
📚 Введение и дополнительные материалы
🟢 Начальный уровень (Rust-basic)
Не прочитана
🔵 Средний уровень (Rust-middle)
38 Unsafe Rust и FFI: работа с сырыми указателями, Undefined Behavior и интеграция с C (текущая)
Не прочитана

Связанные статьи