# Модель владения Rust: безопасность памяти без сборщика мусора

Около 70 % тяжёлых уязвимостей в C и C++ — это нарушения безопасности памяти. Система владения Rust устраняет их во время компиляции, без рантайм-сборщика — и индустрия наконец движется.

Published: 3 октября 2024 г.
Canonical HTML: https://ru.glazastov.com/rust-ownership-model
Markdown: https://ru.glazastov.com/rust-ownership-model.md

Тридцать лет системное программирование жило с компромиссом, который казался фундаментальным: либо ручное управление памятью со всеми его ошибками, либо сборщик мусора со всей его задержкой. Rust отказался выбирать. Его _модель владения_ даёт безопасность управляемого языка и контроль над раскладкой памяти, как в C, — и всё это оплачено целиком во время компиляции. Механизм — небольшой набор правил и компилятор, достаточно упрямый, чтобы их соблюдать.

## Цена, сделавшая идею необходимой

Ошибки памяти — не эстетическая претензия. Это доминирующий источник удалённо эксплуатируемых уязвимостей в коде на C и C++. Microsoft Security Response Center привёл цифру:

> «Около 70 % уязвимостей, которым Microsoft ежегодно присваивает CVE, по-прежнему относятся к ошибкам памяти.» — Мэтт Миллер, _Trends, Challenges, and Shifts in Software Vulnerability Mitigation_, BlueHat IL, февраль 2019.

Команда Chromium в Google независимо сообщает ту же долю:

> «Около 70 % наших серьёзных ошибок безопасности — это проблемы памяти.» — _The Chromium Projects, Memory safety_, текущее издание.

В феврале 2024 Office of the National Cyber Director Белого дома пошёл дальше констатации:

> «Уязвимости памяти — это класс уязвимостей, связанных с тем, как память читается, записывается, выделяется или освобождается. Главное — они предотвратимы. Наиболее эффективный способ устранить весь этот класс — переход производителей ПО к языкам с безопасной работой с памятью.» — ONCD, _Back to the Building Blocks: A Path Toward Secure and Measurable Software_, февраль 2024.

Эмпирическое подтверждение пришло от Android. Когда команда Google начала писать новый платформенный код на Rust, не трогая старый C/C++, доля CVE, связанных с памятью, упала с **76 % всех годовых уязвимостей Android в 2019 году до 24 % в 2024 году**:

> «Доля уязвимостей памяти в Android снизилась с 76 % в 2019 году до 24 % в 2024 году — заметно ниже 70 % индустриальной нормы.» — Джефф Вандер Ступ, _Eliminating Memory Safety Vulnerabilities at the Source_, Google Security Blog, сентябрь 2024.

Старый код продолжает деградировать; новый код просто не производит новых ошибок этого класса.

Именно на этом фоне существует владение. Это не возможность языка. Это аргумент о безопасности.

## Три правила

Каждое значение в Rust подчиняется трём правилам:

1. У каждого значения ровно **один владелец** (привязка).
2. Когда владелец покидает область видимости, значение **уничтожается** — запускается его деструктор, память освобождается.
3. Ссылки на значение подчиняются правилу **алиасинг XOR изменяемость**: в любой момент существует либо одна изменяемая ссылка, либо любое число неизменяемых, но никогда обе разновидности одновременно.

Первые два правила дают детерминированное разрушение (RAII, унаследованное от C++). Третье правило — эксклюзивность пишущего доступа — это собственный вклад языка и причина, по которой гонки данных и большинство инвалидаций итераторов невозможны статически.

Формально для двух живых ссылок $r_1, r_2$ на одно значение:

$$
\neg \bigl( \mathrm{mut}(r_1) \wedge \mathrm{alive}(r_2) \wedge r_1 \neq r_2 \bigr)
$$

Компилятор доказывает это свойство для каждой ссылки в программе. Никаких проверок во время выполнения.

Полное доказательство того, что правила непротиворечивы — что безопасный Rust действительно не способен породить неопределённое поведение, — было дано проектом RustBelt, формализовавшим существенное подмножество языка и стандартной библиотеки в системе доказательств Coq.

> «RustBelt — это первая формальная верификация языка Rust, проверяющая не только систему типов, но и репрезентативный набор библиотек из стандартной библиотеки Rust.» — Юнг, Журдан, Креббер, Дрейер, _RustBelt: Securing the Foundations of the Rust Programming Language_, POPL 2018.

## Семантика перемещения и RAII

```rust
fn main() {
    let s1 = String::from("привет");
    let s2 = s1; // s1 ПЕРЕМЕЩАЕТСЯ в s2

    // println!("{}", s1); // error[E0382]: использование перемещённого значения

    println!("{}", s2); // владельцем стал s2; при выходе из области уничтожен ровно раз
}
```

Присваивание `let s2 = s1` не копирует буфер в куче — оно передаёт владение. Дальше компилятор отслеживает, что `s1` неинициализирован. Когда `s2` покидает область, `Drop::drop` запускается ровно один раз. Нет ни подсчёта ссылок, ни трассирующего сборщика, ни ручного `free`.

Это та же модель RAII, что C++ имеет с 1985 года, — только обеспеченная компилятором. В C++ небрежный конструктор копирования или забытый `std::move` приводят к двойному освобождению или утечке. В Rust borrow-checker отказывается компилировать и то, и другое.

## Заимствование: алиасинг XOR изменяемость

Ссылка в Rust бывает двух видов:

| Форма                     | Запись   | Возможность                                  |
| ------------------------- | -------- | -------------------------------------------- |
| Общая (неизменяемая)      | `&T`     | Сколько угодно одновременно; только чтение   |
| Эксклюзивная (изменяемая) | `&mut T` | Не более одной одновременно; чтение и запись |

Borrow-checker обеспечивает эти свойства как статический type-state каждой привязки. Выигрыш — не только безопасность памяти: оптимизатор вправе предположить, что общие ссылки `&T` не алиасятся ни с какой `&mut T` на ту же область памяти. Это та самая аннотация `noalias`, которую LLVM всегда хотел получить от C, но не мог надёжно. Rust выдаёт её по построению.

Платой служит знаменитая кривая обучения. Шаблоны, тривиально компилирующиеся в Java или Go, — двусвязный список, граф с указателями на родителя, замыкание, захватывающее изменяемую ссылку за пределы своей области, — не выразимы без `unsafe`, `Rc<RefCell<T>>` или редизайна. Сообщество сошлось на том, что это особенность, а не недостаток: такие шаблоны обычно сами были ошибкой.

## Времена жизни

Ссылка не должна жить дольше, чем данные, на которые она указывает. Rust выражает это **параметрами времени жизни**, обобщёнными по областям:

```rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
```

Сигнатура объявляет, что возвращаемая ссылка заимствует из той же области `'a`, что и входные. Отношение между временами жизни задаётся подтипизацией: `'a: 'b` означает «`'a` живёт дольше, чем `'b`». Большинство аннотаций выводится по правилам элизии; явные аннотации появляются лишь там, где связь между входом и выходом действительно неоднозначна.

Времена жизни стираются на этапе компиляции. Они не порождают кода и не занимают памяти во время выполнения. Они существуют только как обязательство доказательства для проверки типов.

## Send, Sync и бесстрашная конкурентность

Маркерные трейты `Send` и `Sync` распространяют рассуждения о владении на потоки:

- `T: Send` — значения типа `T` можно передавать через границы потоков.
- `T: Sync` — `&T` можно разделять между потоками (эквивалентно: `&T: Send`).

Эти трейты выводятся автоматически по структурному составу типа. `Rc<T>` (не-атомарный счётчик ссылок) — `!Send`, потому что одновременное декрементирование счётчика из двух потоков образовало бы гонку. `Arc<T>` — `Send + Sync`, потому что счётчик атомарный. Компилятор на этапе компиляции отказывается запускать поток, захватывающий значение, не реализующее `Send`.

В сочетании с алиасинг-XOR-изменяемостью это даёт то, что сообщество называет **fearless concurrency**: тот же borrow-checker, который предотвращает инвалидацию итератора в однопоточном коде, предотвращает гонки данных в многопоточном — и по той же причине.

## Чего это стоит

У модели владения есть издержки, которые любой честный разбор обязан перечислить.

- **Async и времена жизни.** Заимствования через точки `await` упираются в проблему `Pin` и самореферентных генераторов, окончательно не решённую в языке. Async-функции в трейтах, стабилизированные в конце 2023 года, по-прежнему имеют пробелы в эргономике, которые продвинутые паттерны обходят крейтами вроде `async-trait`.
- **Связные структуры данных.** Классические графы и деревья без `unsafe` требуют арена-аллокаторов или индексов вместо указателей. Это корректно, но не то, чего ждёт программист на C.
- **Время компиляции.** Мономорфизация плюс анализ заимствований делают Rust медленным компилятором. Чистая сборка нетривиального воркспейса — минуты, не секунды.
- **Кривая обучения.** Программистам, свободно владеющим другим системным языком, обычно требуется несколько месяцев, прежде чем они перестают бороться с borrow-checker'ом.
- **`unsafe`.** Корпусное исследование 2020 года показало, что около **29 % крейтов на crates.io содержат хотя бы один блок `unsafe`**, а стандартная библиотека внутри обширно `unsafe`. Владение — это гарантия про _безопасный_ Rust; `unsafe` — люк, где гарантия не действует и где её должен подхватывать человеческий аудит.

Ни одна из этих издержек не смертельна. Все они реальны.

## Где сейчас находится Rust

| Поверхность                   | Состояние                                                                                         |
| ----------------------------- | ------------------------------------------------------------------------------------------------- |
| Ядро Linux                    | Поддержка Rust влилась в 6.1, октябрь 2022; подсистемы драйверов и `rust-for-linux` растут        |
| Windows                       | Модули на Rust в DWriteCore, движке регионов Win32 GDI/GDI+, частях win32k (2023–24)              |
| Платформа Android             | Новый нативный код на Rust; доля CVE памяти упала с 76 % (2019) до 24 % (2024)                    |
| Chromium                      | Rust разрешён для сторонних библиотек с января 2023; first-party-внедрение растёт                 |
| AWS Firecracker, Bottlerocket | Промышленный VMM и ОС для AWS Lambda и Fargate, написаны на Rust                                  |
| Криптография и TLS            | `rustls`, `ring`, `dalek-cryptography`, `RustCrypto`; проаудированы и в продакшене                |
| Встраиваемые системы          | Экосистема `embedded-hal` стабильна; Tock OS в эксплуатации, Hubris в железе Oxide Cloud Computer |

Наиболее цитируемое индустриальное одобрение пришло от технического директора Microsoft Azure:

> «Что касается языков, пора прекратить начинать новые проекты на C/C++ и использовать Rust там, где требуется язык без сборки мусора. Ради безопасности и надёжности индустрия должна объявить эти языки устаревшими.» — Марк Руссинович, сентябрь 2022.

CISA, АНБ и ONCD США опубликовали рекомендации в пользу языков с безопасной работой с памятью для нового кода в критических системах. Rust — единственный язык системного уровня в этой категории с подтверждённым промышленным развёртыванием.

## Тихая часть

Модель владения часто описывают как ловкий трюк. Это не так. Это операционное следствие куда более старой идеи — **линейных типов**, восходящих к линейной логике Жирара (1987) и к работе Уодлера «Linear Types Can Change the World!» (1990): системам типов, отслеживающим использование ресурсов так, чтобы значения нельзя было молча дублировать или отбросить. Rust изобрёл не теорию. Он сделал теорию достаточно эргономичной, чтобы её приняли системные программисты.

Именно это заняло двадцать лет. Borrow-checker — лёгкая половина; синтаксис, сообщения об ошибках, интеграция с редакторами, стандартная библиотека и культурная установка, что компилирующийся код — это пол, а не потолок, — вот что сделало внедрение возможным. Все прочие попытки внести линейные типы в массовый язык провалились не из-за системы типов, а из-за невыносимости использования.

> «Криптографию редко взламывают; её обходят.»
> — Ади Шамир

То же верно и для безопасности в системном коде. C и C++ небезопасны не потому, что их системы типов слабы. Они небезопасны потому, что безопасный путь требует бдительности, которую человек не выдерживает на миллионе строк. Владение снимает это требование.

## Источники

1. Miller, M. _Trends, Challenges, and Shifts in Software Vulnerability Mitigation_. BlueHat IL, Microsoft Security Response Center, февраль 2019. [github.com/Microsoft/MSRC-Security-Research](https://github.com/Microsoft/MSRC-Security-Research/blob/master/presentations/2019_02_BlueHatIL/2019_01%20-%20BlueHatIL%20-%20Trends%2C%20challenge%2C%20and%20shifts%20in%20software%20vulnerability%20mitigation.pdf)
2. The Chromium Projects. _Memory safety_. [www.chromium.org/Home/chromium-security/memory-safety](https://www.chromium.org/Home/chromium-security/memory-safety/)
3. Office of the National Cyber Director. _Back to the Building Blocks: A Path Toward Secure and Measurable Software_. Белый дом, февраль 2024. [bidenwhitehouse.archives.gov/.../Final-ONCD-Technical-Report.pdf](https://bidenwhitehouse.archives.gov/wp-content/uploads/2024/02/Final-ONCD-Technical-Report.pdf)
4. Vander Stoep, J. _Eliminating Memory Safety Vulnerabilities at the Source_. Google Security Blog, сентябрь 2024. [security.googleblog.com](https://security.googleblog.com/2024/09/eliminating-memory-safety-vulnerabilities-Android.html)
5. NSA. _Software Memory Safety Cybersecurity Information Sheet_. ноябрь 2022. [media.defense.gov](https://media.defense.gov/2022/Nov/10/2003112742/-1/-1/0/CSI_SOFTWARE_MEMORY_SAFETY.PDF)
6. Jung, R.; Jourdan, J.-H.; Krebbers, R.; Dreyer, D. _RustBelt: Securing the Foundations of the Rust Programming Language_. POPL 2018. [plv.mpi-sws.org/rustbelt](https://plv.mpi-sws.org/rustbelt/)
7. Astrauskas, V.; Matheja, C.; Poli, F.; Müller, P.; Summers, A. _How Do Programmers Use Unsafe Rust?_ OOPSLA 2020. [doi.org/10.1145/3428204](https://doi.org/10.1145/3428204)
8. Girard, J.-Y. _Linear Logic_. Theoretical Computer Science 50(1), 1987.
9. Wadler, P. _Linear Types Can Change the World!_ In _Programming Concepts and Methods_, North-Holland, 1990.
10. Klabnik, S.; Nichols, C. _The Rust Programming Language_. No Starch Press / rust-lang.org. [doc.rust-lang.org/book](https://doc.rust-lang.org/book/)
11. Torvalds, L. Merge начальной поддержки Rust, Linux 6.1, октябрь 2022. [git.kernel.org](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8aebac82933ff1a7c8eede18cab11e1115e2062b)
12. Russinovich, M. Публичное заявление о принятии Rust, 19 сентября 2022. [twitter.com/markrussinovich/status/1571995117233504257](https://web.archive.org/web/20220920000839/https://twitter.com/markrussinovich/status/1571995117233504257)
