В чем реальная польза Pin в Rust?

Я понимаю, что цель 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::new_unchecked() на самом деле не предназначен для использования конечными пользователями; это скорее деталь реализации внутри библиотек. Самый простой способ создать закрепленный объект конечным пользователем — поместить объект в кучу с помощью Box::pin . Я также написал длинную статью о некоторых мыслях и намерениях Pin, возможно, это поможет.
Finomnis 29.06.2024 14:17

«почему бы просто не использовать Pin и просто дать обещания» — потому что Rust без unsafe гарантированно свободен от неопределенного поведения. Таким образом, в языке должен быть механизм, который представляет это «обещание», которое компилятор может проверить в безопасном коде. Вот почему Pin::new_unchecked() является unsafe, потому что он указывает, что программист библиотеки, который ее использует, несет ответственность за поддержание правильности. Затем другая библиотека, использующая объект Pin, может полагаться на него, не нарушая при этом работоспособность.

Finomnis 29.06.2024 14:21

Но опять же, если вы конечный пользователь, а не программист библиотек, который фактически создает закрепленные объекты на более низком уровне, не используйте Pin::new_unchecked(). Вместо этого используйте Box::pin() или std::pin::pin!().

Finomnis 29.06.2024 14:25
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
93
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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, обещающее это.
  • Код, который использует закрепление — полагаясь на то, что значение не перемещается так, как если бы оно двигалось, это было бы UB.

И даже код, использующий Pin, может инкапсулировать небезопасный код с помощью таких инструментов, как pin!(), Box::pin() и pin-project.

Chayim Friedman 29.06.2024 20:02

Другие вопросы по теме