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

> Изучаем Unsafe Rust и FFI: 5 суперспособностей unsafe, работа с сырыми указателями *const и *mut, Неопределенное поведение (UB), Miri и интеграция с C через extern C.

- **URL:** https://eagle2000.space/rust/rust-unsafe-and-ffi/
- **Date:** 2026-08-11

- **Tags:** rust, dev, unsafe, ffi, raw-pointers, c-bindings, miri

---
## Введение

Гарантии безопасности памяти 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

```rust
// Шаг 1: Создание и разыменование сырых указателей (*const T и *mut T)
fn main() {
    let mut num: i32 = 42;

    // Создание сырых указателей БЕЗОПАСНО и не требует блоков unsafe:
    let ptr_const: *const i32 = &num as *const i32;
    let ptr_mut: *mut i32 = &mut num as *mut i32;

    println!("Адрес памяти (const ptr): {:?}", ptr_const);
    println!("Адрес памяти (mut ptr):   {:?}", ptr_mut);

    // Разыменование и изменение значений требует небезопасного блока unsafe:
    unsafe {
        *ptr_mut = 100;
        println!("Новое значение через *ptr_mut: {}", *ptr_const);
    }
}
```

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


#### Шаг 2

```rust
// Шаг 2: Создание безопасной обертки (Safe Abstraction) над Unsafe кодом
fn swap_raw_pointers(a: &mut i32, b: &mut i32) {
    // Внутренняя реализация использует сырые указатели,
    // но внешняя функция имеет безопасную сигнатуру для пользователя!
    let raw_a: *mut i32 = a;
    let raw_b: *mut i32 = b;

    unsafe {
        let temp = *raw_a;
        *raw_a = *raw_b;
        *raw_b = temp;
    }
}

fn main() {
    let mut x = 10;
    let mut y = 20;

    println!("До обмена: x = {x}, y = {y}");
    swap_raw_pointers(&mut x, &mut y);
    println!("После обмена: x = {x}, y = {y}");
}
```

**2. Безопасные абстракции над Unsafe**
- Главный принцип Rust: использовать `unsafe` внутри небольших хорошо изолированных модулей, предоставляя пользователю безопасный интерфейс.


---

## 3. Неопределенное поведение (Undefined Behavior) и Miri

Если вы пишете `unsafe` код, вы берёте на себя обязанность соблюдать контракты безопасности. Нарушение этих правил ведет к **Undefined Behavior (UB)**:

* Разыменование `null` или висячего указателя.
* Создание невалидного значения (например `bool`, хранящий байт `2`).
* Гонки данных при параллельной записи из разных потоков.

> [!TIP]
> Для автоматической проверки вашего `unsafe` кода на наличие скрытых ошибок памяти используйте инструмент **`cargo miri test`** — интерпретатор синтаксического дерева Rust, который перехватывает любые попытки совершить UB на этапе тестирования.

---

## 4. Интеграция с языком C (FFI и `extern "C"`)

**Foreign Function Interface (FFI)** позволяет вызывать функции из динамических и статических C-библиотек или предоставлять Rust-функции для вызова из C или Python.

Для объявления внешних C-сигнатур используется блок `extern "C"`:

```rust
extern "C" {
    fn abs(input: i32) -> i32;
}
```

А для работы со строками C в модуле `std::ffi` предусмотрены специальный типы: `CStr` (заимствованная C-строка с нулевым байтом `\0` на конце) и `CString` (владеющая C-строка).

### Динамическая загрузка библиотек в Runtime (`libloading`)

Если C-библиотека не слинкована статически во время компиляции, вы можете загружать `.so` / `.dll` / `.dylib` файлы прямо во время выполнения программы с помощью крейта `libloading`:

```rust
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

```rust
use std::os::raw::c_char;

// Шаг 1: Объявление внешних C-функций через unsafe extern "C"
unsafe extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    // Вызов функции abs из системной библиотеки C (libc)
    let negative_number = -42;
    let absolute_val = unsafe { abs(negative_number) };

    println!("Результат работы C-функции abs({negative_number}): {absolute_val}");
}
```

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


#### Шаг 2

```rust
use std::ffi::CStr;
use std::os::raw::c_char;
// Шаг 2: Безопасная работа с C-строками через CStr
fn main() {
    // Си-строка завершается нулевым байтом (null-terminated '\0')
    let c_string_bytes = b"Hello from C!\0";
    
    let c_str = unsafe {
        CStr::from_ptr(c_string_bytes.as_ptr() as *const c_char)
    };

    if let Ok(rust_str) = c_str.to_str() {
        println!("Успешно конвертировано в Rust-строку: '{rust_str}'");
    }
}
```

**2. Конвертация строк при взаимодействии с C**
- В C строки завершаются нулевым байтом `\0`, а в Rust хранят явную длину.
- Типы `CStr` и `CString` из `std::ffi` служат безопасным мостом для передачи текстовых данных между Rust и C.


---

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

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


### 📝 Unsafe Rust и C FFI


**1. Какие специфические операции разрешает ключевое слово `unsafe` в Rust?**

- [ ] Разыменование сырых указателей, вызов unsafe функций/FFI, реализация unsafe типажей и изменение static mut переменных
- [ ] Полное отключение проверки типов компилятора
- [ ] Ускорение выполнения кода за счет отключения оптимизаций

**2. В чем главное отличие сырых указателей (`*const T` / `*mut T`) от обычных безопасных ссылок (`&T` / `&mut T`)?**

- [ ] Сырые указатели могут быть нулевыми (null), невыровненными и игнорируют правила алиасинга
- [ ] Сырые указатели хранятся исключительно в регистре процессора
- [ ] Сырые указатели автоматически очищают память

**3. Что представляет собой Неопределенное поведение (Undefined Behavior / UB)?**

- [ ] Нарушение инвариантов безопасности памяти, из-за чего скомпилированный бинарный код ведет себя непредсказуемо
- [ ] Штатное завершение программы с кодом ошибки 1
- [ ] Пропуск вывода логов в консоль

**4. Какую роль выполняет блок `extern "C"` при написании FFI-кода?**

- [ ] Объявляет сигнатуры функций из сторонних C-библиотек с соблюдением C ABI
- [ ] Компилирует код Rust в исходный код на C
- [ ] Устанавливает соединение с сервером базы данных

