`Pin` - очень запутанная тема, с которой я столкнулся при программировании в Rust. Я старался научиться этому, но руководства, статьи и видео, которые я смотрел, трудно понять. Они обычно связаны с необходимостью знать другую сложную концепцию в Rust, и это заставило меня ходить между статьями, видео и руководствами по другим концепциям.
В этой статье я отфильтрую для Вас все остальные понятия и сосредоточусь исключительно на `pin`. Прочитав эту статью, вы научитесь применять pin в коде и понимать его использование в других исходниках.
Что такое pin
`Pin` является важной особенностью в Rust. Он позволяет разработчикам прикреплять объект к позиции в памяти, чтобы ваши данные не могли переместиться ещё куда-либо.
Эта функция необходима при работе с объектами, ссылающимися на другие объекты, которые имеют тенденцию изменять свое положение в памяти, независимо от того, необходимо это или нет. При построении структур данных, таких как связанные списки или работа с асинхронным кодом, это может повлиять на код и вызвать неопределенное поведение.
Как закрепить объект в памяти?
Закреплять объект можно довольно легко. Rust предоставляет структуру `Pin`, позволяющую закреплять объекты. Структура является частью стандартной библиотеки Rust, доступ к нему можно получить через `std::pin::Pin.`
Рассмотрим следующий пример:
Этот пример содержит простую структуру, которая используется в `main` функции. В `main`, мы создали экземпляр структуры с `value` 10, а затем вывели на печать это значение в консоль.
Мы можем закрепить `my_struct`, `Box::pin()`. И это сделаем в коде ниже.
Я не говорил о `Box`, так что давайте разберемся. `Box` - это структура, позволяющая выделять память в куче.
Rust имеет два типа памяти, которые он использует для хранения значений в коде: стек и куча. Rust хранит данные заданного размера в стеке и данные размеры которых определяются во время выполнения в куче. Память стека имеет ограниченные возможности хранения, но быстрее, чем память в куче. Но память в куче более обширна и гибка, чем память стека.
`Box::pin()` выделяет память в куче и устанавливает данные на постоянное место.
Следующий список содержит сведения, необходимые для закрепления значений:
- Модификация в `pin` объект
- Свойство `_pin`
- Какой риск в использовании `pin` объектов
Давайте рассмотрим все это в деталях!
Изменение данных закрепленного объекта
Одна из вещей, в которой вы застрянете сразу после закрепления объекта - это попытка изменить данные в нем. Это может расстроить, но не бойтесь. Есть способ обойти это, но он включает в себя некоторые процессы нарушения правил. Мы будем выполнять эти процессы в `unsafe` блоке.
Давайте вернемся к последнему примеру, который был у нас в предыдущем разделе.
Допустим, мы хотим изменить значение `my_struct.value` на 32. Если мы просто попытаемся сделать `my_struct.value = 32`, компилятор выдаст сообщение о ошибке, сообщающее, что это не сработает.
Чтобы изменить значение `my_struct.value`, необходимо выполнить несколько шагов:
- Сначала сделайте изменяемой ссылку на закрепленный объект с помощью `Pin::as_mut(&mut my_struct)`.
- Затем используйте эту изменяемую ссылку для ссылки на объект, с помощью `Pin::get_unchecked_mut(mut_ref)`.
- Наконец, используйте ссылку на объект для изменения объекта по своему усмотрению.
Все внесенные изменения будут отражены в закрепленном объекте.
Посмотрим, как выглядит код после выполнения предыдущих шагов.
При выполнении кода отображается значение `my_struct.value` в терминале до и после его изменения.
Свойство `_pin`
Если вы заметили, мы добавили свойство `_pin` в структуру при внесении изменений в наш код. Теперь вы можете спросить себя, что это такое и что оно делает. Это то, что мы рассмотрим в этом разделе.
`_pin` - это свойство, помещаемое в структуру, которую требуется закрепить. Оно сообщает компилятору, что структура должна быть закреплена в памяти по постоянному адресу. Метод `Box::pin()` можно применить к структуре без свойства `_pin`, но он не будет фиксировать ее в памяти.
Вы можете проверить предыдущий это самостоятельно с помощью этого кода:
При использовании свойства `_pin` в структуре и инициализации его с помощью `PhantomPinned` строка 14 (`my_struct.value = 32;`) становится недействительной.
Если вы посмотрите на свойство` _pin`, вы можете спросить, почему необходимо инициализировать его с помощью `PhantomPinned`. Ответ прост: `PhantomPinned` - это тип, который Rust использует для применения правил закрепления структуры в памяти. При необходимости запрещения релокации структуры в памяти, к свойству `_pin` у структуры, всегда следует применять `PhantomPinned`. `PhantomPinned` не содержит значений в памяти и не выполняет никаких действий, кроме применения правил закрепления структуры в памяти, к которой он применяется.
Чем вы рискуете, используя закрепленные в памяти объекты?
Одна из самых больших проблем с закрепленными объектами - безопасность. Не поймите меня неправильно, закрепление объектов перед их использованием в определенных приложениях способствует безопасности. Теперь вы можете задаться вопросом: я только что сказал, что их проблема в безопасности, что происходит? Проблема безопасности с закрепленными предметами заключается в их использовании.
Возможно, вы заметили, что при изменении закрепленного объекта, `my_struct`, пришлось обернуть процессы в `unsafe` блок. На это была причина. Как выясняется, вы должны быть предельно осторожны при вмешательстве в закрепленный в памяти объект. Иначе, вы можете вызвать неопределенное поведение и другие проблемы в основных частях вашего кода.
Наш пример был прост, поэтому рисков было не так много. Но все равно нам понадобилось завернуть его в `unsafe` блок.
Вывод: зачем тогда закреплять объект в памяти?
Теперь, когда вы прошли через все это, остается последний вопрос. Зачем вообще утруждать себя `pin`? Этот вопрос имеет важное значение, поскольку закрепление объектов в памяти по постоянному адресу не будет иметь значения без ответа на них.
Чтобы ответить на эти вопросы, я составил краткий список, в котором говорится о каждой причине:
- Закрепление объекта в Rust гарантирует, что объект останется в фиксированном месте в памяти (жизненно важно для асинхронного программирования).
- Закрепление может помочь предотвратить возникновение гонки данных и других проблем параллелизма при доступе нескольких задач к одним и тем же данным.
- Закрепление также может помочь повысить производительность, сократив копирование и перемещение кода при работе с асинхронными данными.
- Закрепление гарантирует, что определенные типы данных всегда доступны в памяти, даже если компьютер заменяет другие части программы.
Статья на rusty-code.ru