Я понимаю, что цель Pin — предотвратить перемещение значения. Но чтобы правильно использовать Pin::new_unchecked()
, нужно дать много обещаний (например, «данные значения не будут перемещены и его хранилище не станет недействительным, пока оно не будет удалено», DerefMut::deref_mut и Deref::deref для типа указателя). Ptr должен поддерживать инварианты закрепления, и «ссылка на Ptr, на которую разыменовывается, не будет снова перемещена», см. также https://doc.rust-lang.org/std/pin/struct.Pin.html#method. new_unchecked). В противном случае компилятор не может гарантировать, что данные закреплены.
Поскольку вызывающему абоненту в любом случае необходимо дать вышеуказанные обещания, почему бы просто не использовать Pin и просто дать обещания? Какие преимущества на самом деле приносит Pin, помимо простого семантического маркера?
«почему бы просто не использовать Pin и просто дать обещания» — потому что Rust без unsafe
гарантированно свободен от неопределенного поведения. Таким образом, в языке должен быть механизм, который представляет это «обещание», которое компилятор может проверить в безопасном коде. Вот почему Pin::new_unchecked()
является unsafe
, потому что он указывает, что программист библиотеки, который ее использует, несет ответственность за поддержание правильности. Затем другая библиотека, использующая объект Pin
, может полагаться на него, не нарушая при этом работоспособность.
Но опять же, если вы конечный пользователь, а не программист библиотек, который фактически создает закрепленные объекты на более низком уровне, не используйте Pin::new_unchecked()
. Вместо этого используйте Box::pin() или std::pin::pin!().
Pin
— это обязательный договор, запрещающий переезд. Если я поставлю Box<MyType>
и объявлю, что «MyType
является самореферентным и не может быть перемещено», компилятор все равно не помешает пользователю выполнить *my_type_box
и переместить значение. Использование Pin<Box<MyType>>
на самом деле гарантирует, что этого не произойдет за счет ограничения доступа через &mut MyType
и разыменования. Это преимущество Pin
независимо от того, как оно сделано.
Pin::new_unchecked
— это простой способ создания оболочки Pin
, но поскольку она является универсальной для любого типа Deref
, сама сигнатура функции не может гарантировать, что остальная часть контракта Pin
сохраняется. В документации есть примеры, показывающие, как контракт может быть нарушен. Таким образом, он отмечен unsafe
и требует от пользователя предоставления таких гарантий.
Однако Pin::new_unchecked
— не единственный способ создать значение Pin
-d, а на самом деле наиболее распространенным и абсолютно безопасным способом является использование Box::pin
. Box
может сделать эту гарантию самостоятельно, поскольку ее ценность является частной и уникальной. И, как уже упоминалось в комментариях, std::pin::pin!
— это еще один безопасный способ закрепить значение для вещей в стеке.
Так что ничто не заставляет вас использовать Pin::new_unchecked
и обременять себя этими ограничениями.
Поскольку вызывающему абоненту в любом случае необходимо дать вышеуказанные обещания, почему бы просто не использовать Pin и просто дать обещания?
Вся суть типа Pin
заключается в передаче этих обещаний между различными частями (unsafe
) кода.
В рамках одного модуля или ящика вы можете решить все, что захотите, о том, как все работает и какие свойства гарантируются. Но если вы хотите взаимодействовать с другими ящиками и использовать такие обещания из кода unsafe
, вам нужно сделать одно из следующих действий:
Создайте свои функции unsafe
и задокументируйте, как их следует использовать.
/// Safety: ...
pub unsafe fn do_thing_with_pinned_value(value: *mut Foo) {...}
Ограничьте ввод, используя тип, гарантирующий необходимое свойство.
pub fn do_thing_with_pinned_value(value: Pin<&mut Foo>) {...}
В этом смысле Pin
не сильно отличается от любого другого типа Rust. Например, если ваша функция принимает &[bool]
, то этот тип обещает, что указанная память доступна для чтения, не будет изменена и содержит допустимые bool
. Вы можете использовать (*const bool, usize)
вместо &[bool]
, и ваш код будет работать точно так же, но компилятор не сможет обнаружить столько ошибок.
На мой взгляд, причина, по которой Pin
кажется более загадочной, заключается в том, что он предоставляет гарантию, которая требуется реже и потребность которой в гораздо большей степени зависит от конкретного типа, на который полагается. Позвольте мне выразить это в более параллельных терминах:
&mut Foo
обещает, что указывает на действительный Foo
.Pin<&mut Foo>
обещает, что указывает на действительный закрепленный Foo
.Последнее бесполезно, если Foo
не имеет операций, зависящих от закрепления, но полезно, если Foo
действительно зависит от закрепления и используется несколькими ящиками.
В обычном случае Future
фьючерсы определяются ящиками, зависят от закрепления (в частности, блоки async
требуют закрепления для своих локальных переменных), а затем обычно передаются другим ящикам, которые их выполняют. Итак, Pin
— это способ, с помощью которого крейт-исполнитель сообщает крейту-реализатору будущего, что будущее фактически закреплено.
Если бы у нас не было Pin
, то Future::poll
должен был бы быть unsafe fn
, а это означало бы, что все исполнители и будущие комбинаторы должны были бы быть unsafe
кодом. Имея Pin
, мы уменьшаем количество unsafe
кода до:
Pin
, обещающее это.И даже код, использующий Pin
, может инкапсулировать небезопасный код с помощью таких инструментов, как pin!()
, Box::pin()
и pin-project
.
Pin::new_unchecked()
на самом деле не предназначен для использования конечными пользователями; это скорее деталь реализации внутри библиотек. Самый простой способ создать закрепленный объект конечным пользователем — поместить объект в кучу с помощью Box::pin . Я также написал длинную статью о некоторых мыслях и намеренияхPin
, возможно, это поможет.