Модель памяти Rust довольно сильно отличается как от управляемых языков типа Java или C#, так и от не управляемых языков типа C и C++. Так как Rust является совершенно новым языком программирования и не ограничен какими-либо требованиями совместимости с предшественниками, он реализует наиболее удобную модель памяти для решения следующих целей:
Безопасность. Предотвращение возникновения утечек памяти или ошибок сегментации.
Производительность. Сборщик мусора управляет указателями а не объектами. Нет необходимости замораживать все задачи для очистки памяти, так как каждая из них имеет собственный хип.
Многопоточность. Предотвращение возникновения гонок в памяти, так как каждая из задач имеет собственный хип и передаваемые между задачами данные должны копироваться. Наличие хипа обмена для избежания лишних операций копирования с семантикой владения.
Размещение объектов
Rust предоставляет возможность размещения объектов как на стеке, так и в хипе, причем в случае с хипом объекты должны адресоваться либо при помощи разделяемых либо в уникальных указателей.
Использование стека
Так код let x = Point {x: 1f, y: 1f}; разместит объект типа Point на стеке задачи в которой будет вызван. При копировании подобного объекта будет let y = x; будет скопирован не указатель на объект x, а вся структура типа Point.
Использование разделяемых указателей
Разделяемые указатели используются в качестве указателей на объекты располагающиеся в хипе. Разделяемые указатели размещаются в локальном хипе, который является локальным для каждой задачи и никогда не могут быть переданы за ее пределы. Для создания разделяемых указателей используется унарный оператор @
В отличие от стековых объектов, при копировании, копируется исключительно указатель, а не данные.
Так же необходимо отметить тот факт, что невозможно создать структуру, содержащую указатель на собственный тип (классический пример – односвязный список). Для того что бы компилятор разрешил подобную конструкцию, необходимо обернуть указатель в тип Option.
data: T,
nextNode: Option<@LinkedList<T>>
}
Внимание: на данный момент, для управления разделяемыми указателями используется подсчет ссылок, но в будущем планируется переработка данного функционала и использование полноценного сборщика мусора.
Уникальные указатели
Уникальные указатели, как и разделяемые указатели, представляют собой указатели на объекты в хипе, на чем их сходство и заканчивается. Данные адресуемые уникальными указателями располагаются в хипе обмена, который является общим для всех задач. Для создания уникальных указателей используется унарный оператор ~
Уникальные указатели реализуют семантику владения благодаря чему объект может адресовать только один уникальный указатель.
// Указатель x деинициализирован.
При необходимости сделать копию объекта, адресуемого при помощи уникальный указатель, необходимо указать на это компилятору явно.
let y = copy x; // теперь y указывают на копию созданного ранее объекта
// типа Point. Указатель x не изменился.
Уникальные указатели могут быть переданы между задачами, что собственно и является одним из их основных назначений. Изначально, я планировал привести примеры в рамках этой заметки, но с учетом большого объема информации касательно самих задач я сделаю это чуть позже в отдельной заметке.
Временные указатели
Временные указатели – указатели которые могут указывать на любой тип памяти: стек, локальном или хипе обмена, а так же на внутренний член любой структуры данных. На физическом уровне, временные указатели представляют собой типичные Си указатели и, как следствие, не отслеживаются сборщиком мусора и не привносят никаких дополнительных накладных расходов. В то же время, основным отличаем от Си указателей являются дополнительные проверки проводимые на этапе компиляции для гарантии возможности безопасного использования. Для создания временные указателей используется унарный оператор &
// был создан на стеке и временный указатель был сохранен в on_the_stack
Данный код аналогичен следующему:
let on_the_stack_pointer = &on_the_stack;
Типы отличные от стековых приводятся к временному указателям автоматически, без использования оператора взятия адреса.
let managed_box : @Point = @Point {x: 5.0, y: 1.0};
let owned_box : ~Point = ~Point {x: 7.0, y: 9.0};
fn compute_distance(p1: &Point, p2: &Point) -> float {
let x_d = p1.x - p2.x;
let y_d = p1.y - p2.y;
sqrt(x_d * x_d + y_d * y_d)
}
compute_distance(&on_the_stack, managed_box);
compute_distance(managed_box, owned_box);
Так же временные указатели могут указывать на внутренний элемент структуры данных.
Контроль времени жизни временных указателей довольно объемная и не совсем устоявшаяся тема. При желании с ней можно подробно ознакомится в статье Rust Borrowed Pointers Tutorial и Lifetime Notation.
Разыменование
Для доступа к упакованным значениям необходимо проводить операцию разыменования. При доступе к полям структурированных объектов разыменование производится автоматически.
let owned = ~20;
let borrowed = &30;
let sum = *managed + *owned + *borrowed;
В рамках данной заметки я не стал писать про довольно тесно пересекающиеся темы, такие как передача данных адресуемых указателями между задачами и контроль за временем жизни временных указателей. Думаю что в ближайшее время собирусь с мыслями и напишу и про них.
Буду очень благодарен за замечания и предложения по корректному переводу понятий:
- managed box – на данный момент разделяемый указатель;
- owned box – на данный момент уникальный указатель;
- exchange heap – на данный момент хип обмена;
- managed heap – на данный момент локальный хип;
- ownership semantics – на данный момент семантика владения;
- borrowed pointer – на данный момент временные указатели.
“Для создания разделяемых указателей используется унарный оператор ~” – надо поправить на “уникальных указателей”
Спасибо за комментарий!
Exchange heap – разменная куча
Managed heap – управляемая куча
Спасибо!
Спасибо за статью.
Было бы интересно узнать, в чём преимущества подхода уникальных (захватываемых) указателей. Что произойдёт при попытке работать с деинециализированным (перехваченным) указателем?
Я планирую еще написать на эту тему чуть позже.
Ну а сейчас, то можно почитать линки что я привел и поглядеть юнит-тесты в папке с компилятором. К сожалению, на данный момент это лучшая доступная по языку информация.