Найти в Дзене
Ржавый код

Закрепление данных по постоянному адресу в rust

Оглавление

`Pin` - очень запутанная тема, с которой я столкнулся при программировании в Rust. Я старался научиться этому, но руководства, статьи и видео, которые я смотрел, трудно понять. Они обычно связаны с необходимостью знать другую сложную концепцию в Rust, и это заставило меня ходить между статьями, видео и руководствами по другим концепциям.

В этой статье я отфильтрую для Вас все остальные понятия и сосредоточусь исключительно на `pin`. Прочитав эту статью, вы научитесь применять pin в коде и понимать его использование в других исходниках.

Что такое pin

`Pin` является важной особенностью в Rust. Он позволяет разработчикам прикреплять объект к позиции в памяти, чтобы ваши данные не могли переместиться ещё куда-либо.

Эта функция необходима при работе с объектами, ссылающимися на другие объекты, которые имеют тенденцию изменять свое положение в памяти, независимо от того, необходимо это или нет. При построении структур данных, таких как связанные списки или работа с асинхронным кодом, это может повлиять на код и вызвать неопределенное поведение.

Как закрепить объект в памяти?

Закреплять объект можно довольно легко. Rust предоставляет структуру `Pin`, позволяющую закреплять объекты. Структура является частью стандартной библиотеки Rust, доступ к нему можно получить через `std::pin::Pin.`

Рассмотрим следующий пример:

-2

Этот пример содержит простую структуру, которая используется в `main` функции. В `main`, мы создали экземпляр структуры с `value` 10, а затем вывели на печать это значение в консоль.

Мы можем закрепить `my_struct`, `Box::pin()`. И это сделаем в коде ниже.

-3

Я не говорил о `Box`, так что давайте разберемся. `Box` - это структура, позволяющая выделять память в куче.

Rust имеет два типа памяти, которые он использует для хранения значений в коде: стек и куча. Rust хранит данные заданного размера в стеке и данные размеры которых определяются во время выполнения в куче. Память стека имеет ограниченные возможности хранения, но быстрее, чем память в куче. Но память в куче более обширна и гибка, чем память стека.

`Box::pin()` выделяет память в куче и устанавливает данные на постоянное место.

Следующий список содержит сведения, необходимые для закрепления значений:

  • Модификация в `pin` объект
  • Свойство `_pin`
  • Какой риск в использовании `pin` объектов

Давайте рассмотрим все это в деталях!

Изменение данных закрепленного объекта

Одна из вещей, в которой вы застрянете сразу после закрепления объекта - это попытка изменить данные в нем. Это может расстроить, но не бойтесь. Есть способ обойти это, но он включает в себя некоторые процессы нарушения правил. Мы будем выполнять эти процессы в `unsafe` блоке.

Давайте вернемся к последнему примеру, который был у нас в предыдущем разделе.

Допустим, мы хотим изменить значение `my_struct.value` на 32. Если мы просто попытаемся сделать `my_struct.value = 32`, компилятор выдаст сообщение о ошибке, сообщающее, что это не сработает.

Чтобы изменить значение `my_struct.value`, необходимо выполнить несколько шагов:

  1. Сначала сделайте изменяемой ссылку на закрепленный объект с помощью `Pin::as_mut(&mut my_struct)`.
  2. Затем используйте эту изменяемую ссылку для ссылки на объект, с помощью `Pin::get_unchecked_mut(mut_ref)`.
  3. Наконец, используйте ссылку на объект для изменения объекта по своему усмотрению.

Все внесенные изменения будут отражены в закрепленном объекте.

Посмотрим, как выглядит код после выполнения предыдущих шагов.

-4

При выполнении кода отображается значение `my_struct.value` в терминале до и после его изменения.

Свойство `_pin`

Если вы заметили, мы добавили свойство `_pin` в структуру при внесении изменений в наш код. Теперь вы можете спросить себя, что это такое и что оно делает. Это то, что мы рассмотрим в этом разделе.

`_pin` - это свойство, помещаемое в структуру, которую требуется закрепить. Оно сообщает компилятору, что структура должна быть закреплена в памяти по постоянному адресу. Метод `Box::pin()` можно применить к структуре без свойства `_pin`, но он не будет фиксировать ее в памяти.

Вы можете проверить предыдущий это самостоятельно с помощью этого кода:

-5

При использовании свойства `_pin` в структуре и инициализации его с помощью `PhantomPinned` строка 14 (`my_struct.value = 32;`) становится недействительной.

-6

Если вы посмотрите на свойство` _pin`, вы можете спросить, почему необходимо инициализировать его с помощью `PhantomPinned`. Ответ прост: `PhantomPinned` - это тип, который Rust использует для применения правил закрепления структуры в памяти. При необходимости запрещения релокации структуры в памяти, к свойству `_pin` у структуры, всегда следует применять `PhantomPinned`. `PhantomPinned` не содержит значений в памяти и не выполняет никаких действий, кроме применения правил закрепления структуры в памяти, к которой он применяется.

Чем вы рискуете, используя закрепленные в памяти объекты?

Одна из самых больших проблем с закрепленными объектами - безопасность. Не поймите меня неправильно, закрепление объектов перед их использованием в определенных приложениях способствует безопасности. Теперь вы можете задаться вопросом: я только что сказал, что их проблема в безопасности, что происходит? Проблема безопасности с закрепленными предметами заключается в их использовании.

Возможно, вы заметили, что при изменении закрепленного объекта, `my_struct`, пришлось обернуть процессы в `unsafe` блок. На это была причина. Как выясняется, вы должны быть предельно осторожны при вмешательстве в закрепленный в памяти объект. Иначе, вы можете вызвать неопределенное поведение и другие проблемы в основных частях вашего кода.

Наш пример был прост, поэтому рисков было не так много. Но все равно нам понадобилось завернуть его в `unsafe` блок.

Вывод: зачем тогда закреплять объект в памяти?

Теперь, когда вы прошли через все это, остается последний вопрос. Зачем вообще утруждать себя `pin`? Этот вопрос имеет важное значение, поскольку закрепление объектов в памяти по постоянному адресу не будет иметь значения без ответа на них.

Чтобы ответить на эти вопросы, я составил краткий список, в котором говорится о каждой причине:

  • Закрепление объекта в Rust гарантирует, что объект останется в фиксированном месте в памяти (жизненно важно для асинхронного программирования).
  • Закрепление может помочь предотвратить возникновение гонки данных и других проблем параллелизма при доступе нескольких задач к одним и тем же данным.
  • Закрепление также может помочь повысить производительность, сократив копирование и перемещение кода при работе с асинхронными данными.
  • Закрепление гарантирует, что определенные типы данных всегда доступны в памяти, даже если компьютер заменяет другие части программы.

Статья на rusty-code.ru