Введение#
В Rust реализована концепция владения (ownership) определяющая правила работы с данными. Эти правила помогают избежать ошибок при управлении памятью.
В языке Rust каждое значение имеет единственного владельца. Владелец управляет временем жизни данных: когда он выходит из области видимости, память автоматически освобождается. Передача данных между переменными может происходить через копирование copy
(для простых типов) или перемещение move
(для сложных).
Аналогия владения из обычной жизни#
Представьте, что у каждой коробки с данными есть один владелец. Хозяйн коробки может владеть содержимым пока он находится в определенном помещении. Когда владелец покидает помещение и не возвращается за ней (выходит за фигурные скобки), коробка выбрасывается арендодателем (строгим borrow checker-ом). Если вы хотите передать коробку другому, старый владелец теряет к ней доступ (происходит перемещение move
). Чтобы у другого человека появилась копия этой коробки - её нужно обязательно склонировать или создать дубликат.
Пример#
let s1 = String::from("hello");
1
// s1 — владелец строки
let s2 = s1;
2
// данные перемещаются в s2, s1 больше не валидна
// println!("{}", s1);
3
// Ошибка: попытка использовать перемещённое значение
- В данном примере у строки hello появился владелец - переменная
s1
1 - Затем владение передается переменной
s2
2 - Все! Больше переменная s1 ничем не владеет и получить доступ к строке hello не получится.
- Если попробовать вывести 3
значение переменной
s1
то мы получим ошибку.
Вспомним аналогию с коробками:
Владелец с именем s1
👱♂️ коробки со значением строки hello 🔶 решил продать эту коробку. Он разместил объявление на онлайн площадках. Через некоторое время к нему подъехал покупатель с именем s2
👩 и приобрел коробку а также значение строки 🔶 в ней.
👱♂️🔶 –> ➡️ 👩 —-> 👩🔶
Больше коробка никак не связана со старым владельцем 👱♂️ 🚫 🔶. Он только может при желании выкупить ее у нового хозяина коробки s2
👩🔶.
s2
👩🔶 волен распоряжаться ей как угодно: передать во временное хранение, продать, напечатать книгу с содержимым этой коробки.
Примерно так и рассуждает компилятор языка rust когда обрабатывает исходный код приложения.
Основные понятия#
Теперь закрепим основные понятия и определения:
- Владелец (Owner) — это переменная, которая управляет данными (например:
let x = 5;
). - Заимствование (Borrowing) — временное использование данных без передачи владения (например:
&x
). - Правила владения:
- У данных может быть только один владелец в каждый момент времени.
- Можно либо изменять данные, либо читать их, но не одновременно.
- Данные автоматически освобождаются, когда их владелец выходит из области видимости.
- Borrow checker (механизим проверяющий заимствования)
- Это “строгий охранник/арендодатель” в компиляторе языка Rust. Он следит за соблюдением правил владения.
В его задачи входит:
- Предотвращение ошибок
- Использование данных после их освобождения - проблема висячих указателей (dangling pointers).
- Одновременное чтение и изменение данных - гонка данных (data races).
- Утечки памяти.
Он работает на этапе компиляции, поэтому ошибки обнаруживаются до запуска программы.
Стек (Stack) и куча (Heap)#
Для разработки на rust важно понимать устройство работы с памятью.
Выделяется 2 типа памяти: cтек и куча:
О стеке (Stack)#
Как работает:
- Это быстрая и упорядоченная память. В стеке хранятся локальные переменные.
- Работает по принципу “последний вошёл — первый вышел” (LIFO). Представьте стопку тарелок: кладёте по одной наверх и когда понадобилась -> достаете её сверху.
- Размер данных на стеке известен на этапе компиляции (в стеке хранятся простые типы: числа, булевы значения…).
Пример:
fn main() {
let x = 42; // `x` хранится на стеке
let y = 10; // `y` тоже на стеке
}
4
после выхода из блока 4
x
и y
автоматически удаляются
Когда функция завершается, её стековые данные мгновенно очищаются без дополнительных усилий.
О куче (Heap)#
Как работает:
- Это динамическая память, где хранятся данные, размер которых неизвестен заранее (например: строки, векторы, структуры…).
- Выделение и освобождение памяти требует работы (аллокация и деаллокация).
- В Rust для работы с кучей используются Box, Vec, String и другие умные указатели.
- Указатель на данные лежит в стеке, а сами данные — в куче.
Пример:
fn main() {
let s = String::from("Hello"); // данные строки – в куче, указатель – в стеке
}
5
s
выходит из области видимости 5
память автоматически освобождается
Rust сам вызывает деструктор для освобождения памяти.
Различия между стеком и кучей#
Стек | Куча | |
---|---|---|
Скорость | Быстро (аллокация и доступ) | Медленнее (аллокация дороже и сложнее) |
Размер | Фиксированный размер данных | Динамический размер |
Управление | Автоматическое управление | Нужны указатели (Rust сам следит ними) |
Доступ | Локальность (L1/L2 кеш CPU) | Доступ из любой части программы |
Зачем такая сложность?#
Rust строго контролирует доступ к куче через правила владения и заимствования.
Благодаря этому нет утечек памяти: когда что-то покидает стек - куча автоматически очищается.
В rust не нужно вручную вызывать free/delete
(как в C++) или ждать сборщика мусора (как в Golang, Java).
На сегодня это все!
Чтобы не перегружать сразу большим количеством информации о новой концепции, я решил разбить тему владения на несколько статей.