Rust: [Стабилизация] API контактов

Созданный на 7 нояб. 2018  ·  213Комментарии  ·  Источник: rust-lang/rust

@rfcbot fcp слияние
Название функции: pin
Цель стабилизации: 1,32,0
Проблема отслеживания: # 49150
Связанные RFC: rust-lang / rfcs # 2349

Это предложение по стабилизации функции библиотеки pin , делая "закрепление"
API-интерфейсы для управления закрепленной памятью, используемые в стабильной версии.

(Я попытался написать это предложение как исчерпывающий «отчет о стабилизации».)

Стабилизированная функция или API

[std|core]::pin::Pin

Это стабилизирует Pin типа в pin подмодуля std / core . Pin - это
фундаментальная прозрачная оболочка вокруг универсального типа P , который предназначен
быть типом указателя (например, Pin<&mut T> и Pin<Box<T>> оба
действительные, предполагаемые конструкции). Обертка Pin изменяет указатель на "закрепление"
память, к которой он относится, на месте, не позволяя пользователю перемещать объекты
этой памяти.

Обычный способ использования типа Pin - это создание закрепленного варианта некоторого
вид указателя-владельца ( Box , Rc и т. д.). Библиотека std, владеющая указателями all
предоставить конструктор pinned который возвращает это. Затем, чтобы управлять
значение внутри, все эти указатели обеспечивают возможность перехода к Pin<&T>
и Pin<&mut T> . Закрепленные указатели могут быть удалены, возвращая вам &T , но не могут
безопасно изменяемо deref: это возможно только с небезопасным get_mut
функция.

В результате любой, кто изменяет данные с помощью булавки, должен будет поддерживать
неизменный, что они никогда не выходят из этих данных. Это позволяет другому коду
безопасно предполагать, что данные никогда не перемещаются, позволяя им содержать (для
пример) ссылки на себя.

Тип Pin будет иметь следующие стабилизированные API:

impl<P> Pin<P> where P: Deref, P::Target: Unpin

  • fn new(pointer: P) -> Pin<P>

impl<P> Pin<P> where P: Deref

  • unsafe fn new_unchecked(pointer: P) -> Pin<P>
  • fn as_ref(&self) -> Pin<&P::Target>

impl<P> Pin<P> where P: DerefMut

  • fn as_mut(&mut self) -> Pin<&mut P::Target>
  • fn set(&mut self, P::Target);

impl<'a, T: ?Sized> Pin<&'a T>

  • unsafe fn map_unchecked<U, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
  • fn get_ref(self) -> &'a T

impl<'a, T: ?Sized> Pin<&'a mut T>

  • fn into_ref(self) -> Pin<&'a T>
  • unsafe fn get_unchecked_mut(self) -> &'a mut T
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>

impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin

  • fn get_mut(self) -> &'a mut T

Черта подразумевает

Большинство признаков на Pin довольно механически, эти два важны для
его работа:

  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
  • impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { }

std::marker::Unpin

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

Это делает работу с закрепленной ссылкой на то, что
не содержит ссылок на себя, как это было бы с незакрепленными
Справка. Гарантии Pin только для особых типов случаев, таких как
самореферентные структуры: эти типы не реализуют Unpin , поэтому они имеют
ограничения типа Pin .

Известные реализации Unpin в std:

  • impl<'a, T: ?Sized> Unpin for &'a T
  • impl<'a, T: ?Sized> Unpin for &'a mut T
  • impl<T: ?Sized> Unpin for Box<T>
  • impl<T: ?Sized> Unpin for Rc<T>
  • impl<T: ?Sized> Unpin for Arc<T>

Они кодифицируют представление о том, что закрепленность не является транзитивной между указателями. Тот
то есть Pin<&T> закрепляет только фактический блок памяти, представленный T в
место. Иногда пользователей это смущало, и они ожидали, что тип
например, Pin<&mut Box<T>> закрепляет данные T на месте, но закрепляет только
память, к которой на самом деле относится закрепленная ссылка: в этом случае Box
представление, которое является указателем в кучу.

std::marker::Pinned

Тип Pinned - это ZST, который не реализует Unpin ; это позволяет вам
подавить автоматическую реализацию Unpin на стабильной, где !Unpin impls
пока не будет стабильной.

Конструкторы умных указателей

К интеллектуальным указателям std добавляются конструкторы для создания закрепленных ссылок:

  • Box::pinned(data: T) -> Pin<Box<T>>
  • Rc::pinned(data: T) -> Pin<Rc<T>>
  • Arc::pinned(data: T) -> Pin<Arc<T>>

Примечания по закреплению и безопасности

За последние 9 месяцев API-интерфейсы закрепления прошли несколько итераций в виде
мы исследовали их выразительную силу, а также надежность их
гарантии. Теперь я бы с уверенностью сказал, что здесь стабилизировались API закрепления.
добротны и достаточно близки к локальным максимумам по эргономике и
выразительность; то есть готов к стабилизации.

Одна из самых сложных проблем закрепления - это определить, когда безопасно выполнять
проекция булавки : то есть перейти от Pin<P<Target = Foo>> к
Pin<P<Target = Bar>> , где Bar - это поле Foo . К счастью, у нас есть
смог кодифицировать набор правил, которые могут помочь пользователям определить,
проекция безопасна:

  1. Закрепить проект безопасно, только если (Foo: Unpin) implies (Bar: Unpin) : это
    есть, если это не так, что Foo (содержащий тип) равен Unpin а
    Bar (прогнозируемый тип) не Unpin .
  2. Это безопасно, только если Bar никогда не перемещается во время уничтожения Foo ,
    Это означает, что либо Foo не имеет деструктора, либо деструктор тщательно
    проверено, чтобы убедиться, что он никогда не выходит за пределы проецируемого поля.
  3. Это безопасно, только если Foo (содержащий тип) не repr(packed) ,
    потому что это заставляет поля перемещаться, чтобы выровнять их.

Кроме того, API-интерфейсы std не предоставляют безопасного способа закрепления объектов в стеке.
Это связано с тем, что нет возможности безопасно реализовать это с помощью API функции.
Однако пользователи могут небезопасно закреплять вещи в стеке, гарантируя, что они
никогда не перемещайте объект снова после создания закрепленной ссылки.

Ящик pin-utils на crates.io содержит макросы для работы с обоими стеками.
булавка и проекция булавки. Макрос закрепления стека безопасно прикрепляет объекты к
стек, используя трюк с затенением, тогда как макрос для проецирования существует
что небезопасно, но избавляет от необходимости писать шаблон проекции в
что вы могли бы ввести другой неправильный небезопасный код.

Изменения в реализации до стабилизации

  • [] Экспорт Unpin из прелюдии, удаление pin::Unpin реэкспорта

Как правило, мы не реэкспортируем вещи из нескольких мест в std, если только
one - это супермодуль реального определения (например, сокращение
std::collections::hash_map::HashMap на std::collections::HashMap ). За это
причина, реэкспорт std::marker::Unpin из std::pin::Unpin не выполняется
место.

В то же время включены другие важные особенности маркеров, такие как отправка и синхронизация.
в прелюдии. Поэтому вместо реэкспорта Unpin из модуля pin
вставив прелюдию, мы избавляем вас от импорта std::marker::Unpin ,
по той же причине он был помещен в pin .

  • [] Заменить связанные функции на методы

В настоящее время многие функции, связанные с Pin , не используют синтаксис метода.
Теоретически это делается для того, чтобы избежать конфликта с обесцениваемыми внутренними методами. Тем не мение,
это правило не применялось последовательно, и, по нашему опыту, в основном
просто сделал вещи более неудобными. Прикрепленные указатели реализуют только неизменяемые
deref, не изменяемый deref или deref по значению, ограничивая возможность deref
так или иначе. Более того, многие из этих имен довольно уникальны (например, map_unchecked )
и вряд ли конфликт.

Вместо этого мы предпочитаем отдавать методам Pin должный приоритет; пользователи, которые
нужно получить доступ к внутреннему методу всегда можно с помощью UFCS, так же, как они были бы
требуется для доступа к методам Pin, если мы не использовали синтаксис метода.

  • [] Переименуйте get_mut_unchecked в get_unchecked_mut

Текущий порядок несовместим с другими вариантами использования в стандартной библиотеке.

  • [] Удалить impl<P> Unpin for Pin<P>

Этот имплант не оправдывается нашим стандартным обоснованием открепления импов: нет направления указателя между Pin<P> и P . Его полезность покрывается имплементациями для самих указателей.

Этот фьючерсный impl необходимо будет изменить, чтобы добавить P: Unpin .

  • [] Отметить Pin как repr(transparent)

Пин должен быть прозрачной оболочкой вокруг указателя внутри него с тем же представлением.

Связанные функции и более важные вехи

API контактов важны для безопасного управления разделами памяти, которые могут
гарантированно не выселяться. Если объекты в этой памяти не
реализовать Unpin , их адрес никогда не изменится. Это необходимо для
создание самореференциальных генераторов и асинхронных функций. В следствии,
тип Pin появляется в стандартной библиотеке future API и скоро будет
также появляются в API для генераторов (# 55704).

Стабилизация типа Pin и его API является необходимым условием стабилизации
Future API, что само по себе является необходимым предшественником стабилизации
async/await синтаксис и перемещение всей экосистемы futures 0.3 async IO
на стабильный Rust.

Копия @cramertj @RalfJung

T-libs finished-final-comment-period

Самый полезный комментарий

Кто-нибудь готов написать главу о фьючерсах в номиконе? Это кажется невероятно необходимым, учитывая, насколько это тонко. В частности, я думаю, что примеры, особенно примеры ошибочных / плохих реализаций и их несостоятельность, будут проясняющими.

Все 213 Комментарий

@rfcbot fcp слияние

Член команды @withoutboats предложил объединить это. Следующим шагом является проверка остальными членами команды, отмеченными тегами:

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [x] @dtolnay
  • [] @sfackler
  • [x] @withoutboats

Проблемы:

  • box-pinnned-vs-box-pin (https://github.com/rust-lang/rust/issues/55766#issuecomment-443371976)
  • улучшение документации (https://github.com/rust-lang/rust/issues/55766#issuecomment-443367737)
  • naming-of-Unpin решено https://github.com/rust-lang/rust/issues/55766#issuecomment -445074980
  • самостоятельные методы (https://github.com/rust-lang/rust/issues/55766#issuecomment-443368794)

После того, как большинство рецензентов одобрит (и не более двух утверждений остаются нерешенными), наступит последний период для комментариев. Если вы заметили серьезную проблему, которая не поднималась ни на одном этапе этого процесса, сообщите об этом!

См. Этот документ для получения информации о том, какие команды могут дать мне члены команды с тегами.

Спасибо за подробный отзыв здесь @withoutboats! Меня также как бы исторически сбивали с толку различные гарантии Pin , и в настоящее время у меня есть несколько вопросов по поводу стабилизируемых здесь API относительно гарантий безопасности. Чтобы помочь разобраться в этом в своей голове, я решил, что постараюсь записать эти вещи.

Пытаясь начать это записывать, я все время натыкаюсь на стену «что такое Unpin ?» Я немного смущен тем, что это такое, и различными гарантиями, связанными с этим. Можете ли вы еще раз сказать, что это значит для T а также для отказа от реализации Unpin ? Кроме того, если Unpin - безопасная черта для ее реализации, наивно кажется, что ее можно использовать для легкого подрыва небезопасных гарантий Pin<T> , но я наверняка что-то упускаю

если я понял это правильно, каждый тип, который не является самореферентным (т.е. не генератор), открепляется

Это не просто самореферентность, есть и другие варианты использования стабильных адресов памяти, которые Pin также может поддерживать. Однако их относительно немного и они редки.

Насколько я понимаю, безопасная реализация Unpin заключается в том, что, реализуя ее, вы можете нарушить инвариант, необходимый для другого небезопасного кода, который вы написали (что очень важно, только вы можете написать этот небезопасный код, никакой внешний код не может полагаться на то, вы реализовали Unpin ). С безопасным API Pin вы не можете сделать ничего, что могло бы вызвать несостоятельность независимо от того, реализовали вы Unpin . Выбирая использование небезопасного API Pin вы гарантируете, что реализуете Unpin тогда, когда это будет безопасно. Это описано в пункте 1 раздела «Примечания по закреплению и безопасности» выше.

Хм, я все еще не совсем понимаю Unpin . Я сначала просто пытаюсь понять, что значит внедрять или не побуждать Unpin .

Во-первых, вероятно, полезно знать, какие типы реализуют Unpin автоматически! Выше упоминалось, что общие типы указателей (Arc / Rc / Box / links) реализуют Unpin , но я думаю, что это так? Если это автоматическая характеристика, означает ли это, что тип MyType автоматически реализует Unpin если он содержит только указатели? Или никакие другие типы не реализуют автоматически Unpin ?

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

Я думаю, что понимаю гарантии Pin<P> когда вы не можете выйти из любого из встроенных членов P::Target , но так ли это?

@alexcrichton Спасибо за вопросы, я уверен, что API-интерфейсы закрепления могут немного сбивать с толку людей, которые не были частью группы, занимающейся ими.

Во-первых, вероятно, будет полезно узнать, какие типы автоматически отключают открепление!

Открепление - это автоматическая черта, такая как Send или Sync, поэтому большинство типов реализуют ее автоматически. Генераторы и типы асинхронных функций: !Unpin . Типы, которые могут содержать генератор или тело асинхронной функции (другими словами, типы с параметрами типа), также не являются автоматически Unpin если не являются их параметры типа.

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

Может ли кто-нибудь помочь, еще раз повторив, что значит реализовать Unpin, а также что значит не реализовывать Unpin?

Вот своего рода фундаментальная идея API закрепления. Во-первых, учитывая тип указателя P , Pin<P> действует как P за исключением того, что если P::Target реализует Unpin , взаимное разыменование небезопасно. Pin<P> . Во-вторых, есть два основных инварианта, которые должен поддерживать небезопасный код, связанный с Pin :

  • Если вы небезопасно получили &mut P::Target от Pin<P> , вы никогда не должны перемещать P::Target .
  • Если вы можете построить Pin<P> , необходимо гарантировать, что вы никогда не сможете получить незакрепленный указатель на данные, на которые указывает указатель, пока не запустится деструктор.

Из всего этого следует, что если вы построите Pin<P> , значение, на которое указывает P больше никогда не переместится, что является гарантией, которая нам нужна как для самореферентных структур, так и для навязчивых коллекции. Но вы можете отказаться от этой гарантии, просто добавив Unpin для своего типа.

Таким образом, если вы реализуете Unpin для типа, вы говорите, что тип отказывается от любых дополнительных гарантий безопасности Pin - возможно взаимное разыменование указателей, указывающих на него. Это означает, что вы говорите, что тип не обязательно должен быть неподвижным для безопасного использования.

Перемещение типа указателя, например Rc<T> , не перемещает T поскольку T находится за указателем. Точно так же закрепление указателя на Rc<T> (как в Pin<Box<Rc<T>> ) на самом деле не закрепляет T , а только закрепляет этот конкретный указатель. Вот почему все, что сохраняет свои дженерики за указателем, может реализовать Unpin даже если их дженерики этого не делают.

Кроме того, если Unpin - безопасная черта для его реализации, наивно кажется, что ее можно использовать, чтобы легко подорвать небезопасные гарантии Pin, но я наверняка что-то упускаю

Это была одна из самых сложных частей API закрепления, и мы сначала ошиблись.

Открепить означает «даже после того, как что-то было помещено в булавку, можно безопасно получить изменяемую ссылку на это». Существует еще одна особенность, которая дает вам такой же доступ: Drop . Итак, мы выяснили, что поскольку Drop безопасен, Unpin также должен быть безопасным. Подрывает ли это всю систему? Не совсем.

Для фактической реализации самореферентного типа потребовался бы небезопасный код - на практике единственные самореферентные типы, о которых кто-то заботится, - это те, которые генерирует для вас компилятор: генераторы и конечные автоматы асинхронных функций. В них явно говорится, что они не реализуют Unpin и не имеют реализации Drop , поэтому вы знаете, что для этих типов, если у вас есть Pin<&mut T> , они будут никогда не получают изменяемую ссылку, потому что это анонимный тип, который, как мы знаем, не реализует Unpin или Drop.

Проблема возникает, когда у вас есть структура, содержащая один из этих анонимных типов, например будущий комбинатор. Чтобы перейти от Pin<&mut Fuse<Fut>> к Pin<&mut Fut> , вы должны выполнить «проекцию булавки». Здесь вы можете столкнуться с проблемой: если вы прикрепите проект к будущему полю комбинатора, но затем реализуете Drop для комбинатора, вы можете выйти из поля, которое должно быть закреплено.

По этой причине выступание булавками небезопасно! Чтобы выполнить проекцию булавки, не нарушая инвариантов закрепления, вы должны гарантировать, что никогда не сделаете несколько вещей, которые я перечислил в предложении по стабилизации.

Итак, tl; dr: Drop существует, поэтому Unpin должно быть в безопасности. Но это не портит все, это просто означает, что проекция булавки составляет unsafe и любой, кто хочет закрепить проект, должен поддерживать набор инвариантов.

генераторы и конечные автоматы асинхронных функций. В них явно говорится, что они не реализуют Unpin и у них нет реализации Drop, поэтому вы знаете, что для этих типов, если у вас есть Pin <& mut T>, они никогда не получат изменяемую ссылку, потому что они анонимный тип, который, как мы знаем, не реализует Unpin или Drop.

Разве асинхронный конечный автомат не должен иметь реализацию Drop ? То, что находится в «стеке» асинхронной функции (которая, вероятно, равна полям в конечном автомате), должно быть уничтожено, когда асинхронная функция завершается или отменяется. Или так бывает иначе?

Я предполагаю, что в этом контексте имеет значение, существует ли элемент impl Drop for Foo {…} , который запускал бы код с &mut Foo который мог бы использовать, например, mem::replace для «эксфильтрации» и перемещения Foo значение.

Это не то же самое, что «капля клея», которую можно вызвать через ptr::drop_in_place . Drop glue для данного типа Foo вызовет Drop::drop если он реализован, а затем рекурсивно вызовет drop glue для каждого поля. Но эти рекурсивные вызовы никогда не включают &mut Foo .

Кроме того, хотя генератор (и, следовательно, асинхронный конечный автомат) имеет настраиваемый клей для перетаскивания, он просто отбрасывает правильный набор полей на основе текущего состояния, он обещает никогда не перемещать ни одно из полей во время перетаскивания.

Терминология, которую я использую (хотя я не думаю, что существует какой-либо стандарт): «drop glue» - это рекурсивный обход полей, генерируемый компилятором, вызывающий их деструкторы; «Реализация drop» - это реализация трейта Drop , а «деструктор» - это комбинация клея drop и реализации Drop. Клей drop никогда ничего не перемещает, поэтому нас интересуют только реализации Drop.

Кто-нибудь готов написать главу о фьючерсах в номиконе? Это кажется невероятно необходимым, учитывая, насколько это тонко. В частности, я думаю, что примеры, особенно примеры ошибочных / плохих реализаций и их несостоятельность, будут проясняющими.

@Gankro Конечно, ты можешь меня за это выставить.

Всем спасибо за объяснение!

Лично я новичок в async Rust и API-интерфейсах Pin, но в последние дни я немного поигрался с ними (https://github.com/rust-lang-nursery/futures-rs/pull/1315 - где Я пытался использовать закрепление для навязчивых коллекций в асинхронном коде).

Во время экспериментов у меня возникли некоторые опасения по поводу этих API:

  • Идея закрепления кажется очень сложной для полного понимания, даже если кто-то знает, что требуются стабильные адреса памяти. Некоторые проблемы, с которыми я столкнулся:

    • Сообщения об ошибках вроде:

      > внутри impl core::future::future::Future+futures_core::future::FusedFuture признак std::marker::Unpin не реализован для std::marker::Pinned

      Это звучит довольно рекурсивно и противоречиво (почему то, что закреплено, не должно быть закреплено?)

    • Как я могу получить доступ к изменяемым полям в моем методе, который принимает ПИН в качестве параметра. Потребовался небольшой поиск, изучение новых терминов (проекция Pin ), попытки скопировать методы из pin-utils , пока я не пришел к решению, которое использует 2 небезопасных метода для выполнения того, что мне нужно. .

    • Как я могу использовать свое будущее в методе async и случае select . Оказалось, что мне нужно pin_mut!() в стек фьючерсов. На самом деле это не стек, что сбивает с толку.

    • Почему мой метод drop() теперь снова принимает &mut self вместо Pin .

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

  • Эта концепция широко распространяется по всей кодовой базе, что, кажется, заставляет многих людей понять, что Unpin самом деле представляют собой Pin и Unpin . Это похоже на небольшую утечку деталей реализации для некоторых более особых случаев использования.
  • Пин, кажется, отчасти связан со сроками жизни, но также отчасти ортогонален. По сути, Пин сообщает нам, что мы можем снова увидеть объект с точно таким же адресом, пока не будет вызван drop() . Это дает ему какое-то виртуальное время жизни между текущей областью видимости и 'static . Кажется запутанным иметь две взаимосвязанные концепции, но все же совершенно разные. Я подумал, можно ли это смоделировать чем-то вроде 'pinned .

Во время компиляции я часто думал о C ++, где можно избежать большинства этих проблем: типы можно объявить неподвижными, удалив конструктор перемещения и оператор присваивания. Когда тип не может быть перемещен, типы, содержащие этот тип, также не могут быть перемещены. Таким образом, свойство и требования изначально проходят через иерархию типов и проверяются компилятором вместо пересылки свойства внутри некоторых вызовов (не всех, например, не drop() ). Я думаю, это намного легче понять. Но, может быть, это неприменимо к Rust, поскольку Future настоящее время часто нужно перемещать, прежде чем что-то начнет их опрашивать? Но с другой стороны, это можно исправить с помощью нового определения с этим признаком или разделения фазы движения и опроса.

Относительно комментария Алекса Unpin : я медленно понимаю, что означает Unpin , но согласен с тем, что это трудно понять. Может быть, другое имя поможет, но я не могу сейчас найти лаконичное. Что-то вроде ThingsInsideDontBreakIfObjectGetsMoved , DoesntRequireStableAddress , PinDoesntMatter и т. Д.

Я еще не до конца понял, почему получение &mut self из Pin<&mut self> не может быть безопасным для всех типов. Если Pin будет означать, что адрес самого объекта стабильный, то это должно быть верно для большинства типичных типов Rust. Похоже, что в Pin интегрирована еще одна проблема, заключающаяся в том, что самореферентные типы внутри него не ломаются. Для тех типов манипулирование ими после наличия Pin всегда небезопасно. Но я думаю, что в большинстве случаев они будут генерироваться компилятором, и небезопасные или даже необработанные указатели там должны быть в порядке. Для будущих комбинаторов вручную, которым необходимо перенаправлять закрепленные вызовы в подполя, очевидно, потребуются небезопасные вызовы для создания контактов к полям перед их опросом. Но в этом случае я считаю, что небезопасность больше связана с тем местом, где это действительно важно (действует ли пин-код?), Чем с доступом к другим полям, для которых это не имеет никакого значения.

Еще одна мысль, которая у меня возникла, - можно ли получить более простую систему, если применить некоторые ограничения. Например, если вещи, требующие закрепления, могут использоваться только внутри асинхронных методов, а не с будущими комбинаторами. Возможно, это может упростить задачу до одного из Pin или Unpin . Учитывая, что для большого количества кода методы async / await с поддержкой select / join могут быть выгодны комбинаторам, это может быть не самая большая потеря. Но я не особо задумывался, действительно ли это помогает.

С положительной стороны: возможность писать код async / await с заимствованиями «стека» - это круто и очень необходимо! А возможность использовать закрепление для других сценариев использования, таких как навязчивые коллекции, улучшит производительность, а также такие цели, как встроенные системы или ядра. Так что я действительно с нетерпением жду решения по этому поводу.

Незначительный нюанс по поводу отчета о стабилизации:

fn get_unchecked_mut(self) -> &'a mut T

Я предполагаю, что это на самом деле unsafe fn ?

@ Matthias247, вы не можете безопасно получить & mut T из Pin<P<T>> если T не Unpin, потому что тогда вы могли бы mem::swap переместить T из булавки, что нарушило бы цель закрепления.

Одна вещь, которую я не могу объяснить себе, - это то, что отличает Future от других черт, так что Pin должен быть частью его API. Я имею в виду, я знаю интуитивно, потому что async / await требует закрепления, но говорит ли это что-то конкретно о Futures, что отличается от, скажем, итераторов?

Может ли опрос принимать & mut self и реализовывать Future только для Pin<P> типов или типов, которые откреплены?

Как я могу получить доступ к изменяемым полям в моем методе, который принимает ПИН в качестве параметра.

Это на самом деле заставляет меня задуматься, есть ли пара отсутствующих методов в Pin ,

impl<'a, T: ?Sized> Pin<&'a T> {
    fn map<U: Unpin, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
}

impl<'a, T: ?Sized> Pin<&'a mut T> {
    fn map<U: Unpin, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
}

Их можно использовать в макросе pin_utils::unsafe_unpinned! .

Я пытаюсь понять, почему этот макрос считается небезопасным. Если у нас !Unpin и есть поле Unpin , почему было бы небезопасно проецировать на это поле?

Единственная ситуация, когда я вижу, что это ненадежно, - это реализация пользовательского типа !Unpin , взяв необработанный указатель на поле Unpin объекта self (и полагаясь на то, что он имеет стабильный адрес / указывает тот же экземпляр), затем получает &mut в то же поле и передает его внешней функции. Похоже, это подпадает под те же рассуждения о том, почему реализация Unpin безопасна, принимая необработанный указатель на поле !Unpin вы отказываетесь от возможности вызывать некоторые из безопасных API.

Чтобы сделать предыдущую ситуацию более безопасной, могла бы быть оболочка, построенная на Pinned для маркировки, которая обычно Unpin поля структуры на самом деле !Unpin вместо простого добавления Pinned в структуру в целом:

pub struct MustPin<T: Unpin>(T, Pinned);

impl<T: Unpin> MustPin<T> {
    pub const fn new(t: T) -> Self { ... }
    pub fn get(self: Pin<&Self>) -> *const T { ... }
    pub fn get_mut(self: Pin<&mut Self>) -> *mut T { ... }
}

Все это кажется обратно совместимым с текущим API, это могло бы устранить некоторую небезопасность комбинаторов futures-rs (например, это обеспечило бы безопасный доступ к дополнительным полям, подобным этому ), но это не обязательно для текущих сценариев использования. Мы могли бы, вероятно, поэкспериментировать с некоторыми подобными API-интерфейсами для реализации пользовательских типов !Unpin (например, навязчивых коллекций) и добавить их позже.

@ Nemo157 Эти функции карты небезопасны, потому что я могу mem::swap на &mut T функция передает мне, прежде чем вернуть &mut U . (ну, изменчивый, неизменный может быть небезопасным)

РЕДАКТИРОВАТЬ: Также отличается макрос pin-utils, unsafe_unpinned не имеет отношения к целевому типу Unpin , это просто «незакрепленная проекция» - проекция на &mut Field . Его безопасно использовать, если вы никогда не прикрепляете проект к этому полю, даже если это поле !Unpin .

Одна вещь, которую я не могу объяснить себе, - это то, что отличает Future от других черт, так что Pin должен быть частью его API. Я имею в виду, я знаю интуитивно, потому что async / await требует закрепления, но говорит ли это что-то конкретно о Futures, что отличается от, скажем, итераторов?

Теоретической разницы нет, но есть прагматические. Во-первых, Iterator стабильно. Но в результате, если мы не придумаем что-то очень умное, вы никогда не сможете запустить цикл for над самореферентным генератором без двойного косвенного обращения, даже если это должно быть полностью безопасно без него ( потому что цикл for будет потреблять и никогда не перемещать генератор).

Еще одно важное прагматическое отличие состоит в том, что шаблоны кода между Iterator и Future совершенно разные. Вы не можете потратить 10 минут, не желая занять точку ожидания в будущем, поэтому на фьючерсах 0.1 вы видите, что эти Arc появляются повсюду, поэтому вы можете использовать одну и ту же переменную в двух разных and_then звонки. Но мы получили довольно далеко , не будучи в состоянии выразить самосправочные итераторы вообще, это просто не то, что важно в прецеденте.

Эти функции карты небезопасны, потому что я могу mem::swap на &mut T функция передает мне, прежде чем вернуть &mut U

Ах, блин, забыл про эту часть: frowning:

fn as_mut(&mut self) -> Pin<&mut P::Target> fn into_ref (self) -> Пин <& 'a T> `

Следует ли называть их as_pinned_mut и into_pinned_ref , чтобы не путать их с методами, которые фактически возвращают ссылки?

Известные реализации Unpin в std:

Еще у нас есть impl<P> Unpin for Pin<P> {} . Для типов, которые мы используем, это не влияет и кажется немного безопаснее?
Неважно, это у вас в списке. ;)

Гарантия падения

Я думаю, мы должны кодифицировать гарантию Drop до стабилизации, иначе будет слишком поздно:

Недопустимо аннулировать хранилище закрепленного объекта (цель ссылки Pin ) без вызова drop для объекта.

«Недействительный» здесь может означать «освобождение», но также и «перепрофилирование»: при изменении x с Ok(foo) на Err(bar) хранилище foo становится недействительным. .

Это приводит, например, к тому, что становится незаконным освободить Pin<Box<T>> , Pin<Rc<T>> или Pin<Arc<T>> без предварительного вызова drop::<T> .

Перепрофилирование Deref , DerefMut

Мне все еще немного не по себе, как это перепрофилирует черту Deref чтобы она означала «это умный указатель». Мы используем Deref для других вещей, например, для «наследования». Это может быть антипаттерн, но он все еще довольно широко используется и, честно говоря, полезен. : D

Я думаю, что это не вопрос надежности.

Понимание Unpin

Бесстыдный плагин: я написал два сообщения в блоге об этом, которые могут помочь или нет, в зависимости от того, насколько вам нравится читать (полу) формальный синтаксис. ;) С тех пор API изменились, но основная идея осталась прежней.

Безопасные выступы булавок

@ Matthias247 Я думаю, что одна из проблем, с которой вы столкнулись, заключается в том, что создание абстракций, включающих закрепление, в настоящее время почти всегда требует небезопасного кода. Использовать эти абстракции можно, но, например, определение будущего комбинатора в безопасном коде не сработает. Причина этого в том, что «проекции выводов» имеют ограничения, которые мы можем безопасно проверить только с изменениями компилятора. Например, вы спрашиваете

Почему мой метод drop () теперь снова принимает & mut self вместо Pin.

Что ж, drop() старый - он существует с Rust 1.0, поэтому мы не можем его изменить. Мы бы хотели, чтобы для этого потребовалось Pin<&mut Self> , а затем Unpin типы могли получить свои &mut как сейчас, но это изменение не имеет обратной совместимости.

Чтобы сделать написание комбинаторов будущего безопасным, нам понадобятся безопасные проекции выводов, и для этого нам нужно изменить компилятор, этого нельзя сделать в библиотеке. Мы могли бы почти сделать это в derive proc_macro, за исключением того, что нам понадобится способ утверждения, что "этот тип не имеет никакой реализации Drop или Unpin ".

Думаю, стоит потрудиться, чтобы выяснить, как получить такой безопасный API для проекций булавок. Однако это не обязательно должно блокировать стабилизацию: API, который мы здесь стабилизируем, должен быть совместим с безопасными проекциями булавок. ( Unpin возможно, потребуется стать элементом lang для реализации приведенного выше утверждения, но это не так уж плохо.)

Закрепления нет !

Я часто думал о C ++, где можно избежать большинства этих проблем: типы можно объявить неподвижными, удалив конструктор перемещения и оператор присваивания. Когда тип не может быть перемещен, типы, содержащие этот тип, также не могут быть перемещены.

Было несколько попыток определить неподвижные типы для Rust. Это очень быстро усложняется.

Важно понимать, что закрепление не обеспечивает неподвижных типов! В Rust все типы остаются подвижными. Если у вас есть T , вы можете переместить его куда угодно. Вместо неподвижных типов мы используем способность Rust определять новые инкапсулированные API, поскольку мы определяем тип указателя, из которого вы не можете выйти . Вся магия заключается в типе указателя ( Pin<&mut T> ), а не в указателе ( T ). Типа не может сказать: «Я не могу двигаться никогда». Однако, есть способ для типа сказать «Если бы я был прижат, не двигайте меня снова». Так в MyFuture , что у меня есть всегда будет подвижна, но Pin<&mut MyFuture> является указателем на экземпляр MyFuture , что не перемещаются больше.

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

Но мы зашли довольно далеко, не имея возможности вообще выражать самореферентные итераторы, это просто не так важно для варианта использования.

Это потому, что до сих пор все итераторы определялись с использованием типа и блока impl Iterator for … . Когда необходимо сохранить состояние между двумя итерациями, нет другого выбора, кроме как поместить его в поля типа итератора и работать с &mut self .

Однако, даже если это не основная мотивация для включения генераторов в язык, было бы очень хорошо в конечном итоге иметь возможность использовать синтаксис генератора yield для определения чего-то, что можно использовать с for loop. Как только это существует, заимствование через yield потому что это так же важно для генераторов-итераторов, как и для генераторов-фьючерсов.

( Unpin возможно, потребуется стать элементом lang для реализации приведенного выше утверждения, но это не так уж плохо.)

Unpin и Pin уже должны быть элементами lang для поддержки безопасных неподвижных генераторов.

Хорошо, спасибо за все объяснения! Я согласен с @Gankro, что глава в стиле номикона о Pin и тому подобном была бы здесь очень полезна. Я подозреваю, что существует большая история разработки того, почему существуют различные безопасные методы и почему небезопасные методы небезопасны и тому подобное.

Чтобы помочь себе понять это, я хотел снова попытаться записать, почему каждая функция безопасна или почему она unsafe . С пониманием сверху у меня есть все следующее, но тут и там есть несколько вопросов. Если бы другие могли помочь мне заполнить это, было бы здорово! (или помогите указать, где я ошибаюсь)

  • fn new(P) -> Pin<P> where P: Deref, P::Target: Unpin

    • Это безопасный метод построения Pin<P> , и пока наличие
      Pin<P> обычно означает, что P::Target больше никогда не будет перемещен в
      программа P::Target реализует Unpin который считывается "гарантиями
      Pin больше не удерживаются ". В результате здесь безопасность, потому что нет
      гарантии, чтобы все могло продолжаться как обычно.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • В отличие от предыдущей, эта функция unsafe потому что P не
      обязательно реализовать Unpin , поэтому Pin<P> должен поддерживать гарантию того, что
      P::Target никогда больше не переместится после создания Pin<P> .

    Тривиальный способ нарушить эту гарантию, если эта функция безопасна, выглядит
    нравиться:

    fn foo<T>(mut a: T, b: T) {
        Pin::new_unchecked(&mut a); // should mean `a` can never move again
        let a2 = mem::replace(&mut a, b);
        // the address of `a` changed to `a2`'s stack slot
    }
    

    Следовательно, пользователь должен гарантировать, что Pin<P> действительно означает
    P::Target больше никогда не перемещается после строительства, поэтому это unsafe !

  • fn as_ref(&Pin<P>) -> Pin<&P::Target> where P: Deref

    • Учитывая Pin<P> у нас есть гарантия, что P::Target никогда не переместится. Это
      часть контракта Pin . В результате это тривиально означает, что
      &P::Target , другой "умный указатель" на P::Target предоставит то же самое.
      гарантия, поэтому &Pin<P> можно безопасно перевести в Pin<&P::Target> .

    Это общий метод перехода от Pin<SmartPointer<T>> к Pin<&T>

  • fn as_mut(&mut Pin<P>) -> Pin<&mut P::Target> where P: DerefMut

    • Я считаю, что безопасность здесь примерно такая же, как у as_ref выше.
      Мы не раздаем изменяемый доступ, просто Pin , так что ничего не может быть легко
      нарушено еще.

    Вопрос : а как насчет "вредоносного" DerefMut impl? Это безопасный способ
    для вызова предоставленного пользователем DerefMut который изначально упаковывает &mut P::Target ,
    предположительно позволяя ему также изменять его. Как это безопасно?

  • fn set(&mut Pin<P>, P::Target); where P: DerefMut

    • Вопрос : Учитывая Pin<P> (и тот факт, что мы ничего не знаем о
      Unpin ), не должно ли это гарантировать, что P::Target никогда не перемещается? Если мы можем
      повторно инициализировать его с другим P::Target , но разве это не должно быть небезопасно?
      Или это как-то связано с деструкторами и прочим?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • Это функция unsafe поэтому главный вопрос здесь: "Почему это не
      safe "? Пример нарушения гарантий, если это было безопасно, выглядит так:

    ...

    Вопрос : какой здесь контрпример? Если бы это было безопасно, что
    пример, демонстрирующий нарушение гарантий Pin ?

  • fn get_ref(Pin<&'a T>) -> &'a T

    • Гарантия Pin<&T> означает, что T никогда не переместится. Возврат &T
      не допускает мутации T , поэтому это должно быть безопасно при сохранении
      это гарантия.

    Одна из возможных ошибок - внутренняя изменчивость. Что, если бы T были
    RefCell<MyType> ? Однако это не нарушает гарантий
    Pin<&T> потому что гарантия распространяется только на T в целом, а не на
    внутреннее поле MyType . В то время как внутренняя изменчивость может перемещаться
    внутренности, он по-прежнему принципиально не может переместить всю структуру за
    & ссылка.

  • fn into_ref(Pin<&'a mut T>) -> Pin<&'a T>

    • Pin<&mut T> означает, что T никогда не переместится. В результате это означает Pin<&T>
      дает такую ​​же гарантию. Это преобразование не должно вызывать особых проблем,
      это в основном переключение типов.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> означает, что T никогда не должен двигаться, так что это тривиально unsafe
      потому что вы можете использовать mem::replace в результате для перемещения T (безопасно). В
      unsafe здесь "хотя я даю вам &mut T , вам не разрешено никогда
      переместить T ".
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>

    • Я думаю, что unsafe здесь, по крайней мере, такое же, как указано выше, мы
      безопасная раздача &mut T (небезопасное закрытие не требуется), что может
      легко использовать с mem::replace . Возможно, здесь есть и другая небезопасность
      с проекцией, но кажется разумным, что это как минимум небезопасно
      из-за этого.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • Реализуя Unpin тип говорит: " Pin<&mut T> имеет никаких гарантий, это
      просто оболочка newtype &mut T ". В результате без каких-либо гарантий
      Поддержите, мы можем вернуть &mut T безопасно
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • Это можно безопасно реализовать с помощью as_ref за которым следует get_ref , поэтому
      Безопасность этого следует из вышеизложенного.
  • impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }

    • Это можно безопасно реализовать с помощью as_mut за которым следует get_mut , поэтому
      Безопасность этого следует из вышеизложенного.
  • impl<T: ?Sized> Unpin for Box<T> (и другие реализации, связанные с указателями)

    • Если не было предпринято никаких других действий, Box<T> реализует трейт Unpin
      только если T реализовано Unpin . Эта реализация здесь кодифицирует, что даже
      если T явно не реализует Unpin , Box<T> реализует Unpin .

    Вопрос : Каков пример того, что это существенно расширяет возможности?

    Например, для того, что безопасно, в противном случае потребовалось бы unsafe если бы это

    не существует.


@ Matthias247, ты не можешь безопасно получить & mut T от Пина

> если T не Unpin, потому что тогда вы могли бы mem :: swap переместить T из булавки, что нарушило бы цель закрепления вещей.

Благодаря! Да, поскольку swap является небезопасным методом, это очевидно проблематично. Но можно ли это исправить, добавив Unpin привязанный к swap() ? Поскольку весь код до сих пор должен быть Unpin или в любом случае небезопасен, это не должно ничего сломать.

Думаю, одна из вещей, которая все еще меня больше всего смущает, - это то, что Pin<T> кодифицирует несколько гарантий: стабильный адрес T, а также некоторые гарантии относительно его внутреннего состояния (в некоторых случаях он заморожен / неизменен). ситуаций, но тоже не совсем).

Мне казалось, что перемещение небезопасного кода / проекций только в те места, где требуются дальнейшие вызовы на основе Pin (например, в poll s в полях), может быть предпочтительнее, чем иметь дело с этими небезопасными проекциями в все дела. Однако теперь я также понял, что есть еще одна проблема: код, имеющий доступ к изменяемой ссылке, может свободно перемещать поле, и когда drop() затем безопасно вызывается в этом поле, оно может сломаться из-за этого. адрес хранится в другом месте. Для того, чтобы исправить это, потребуется перегрузка drop(Pin<T>) о которой говорилось.

@RalfJung Спасибо за объяснения! Я согласен с тем, что я определенно пытался сделать что-то небезопасное в целом, и поэтому вполне нормально потребовать от меня дополнительного понимания. Меня больше беспокоят люди, которые хотят написать более общие безопасные комбинаторы будущего, но теперь также могут столкнуться со всеми этими терминами. Если они могут писать комбинаторы, вообще не разбираясь в Unpin и проекциях контактов, а затем получают только комбинаторы, которые работают в ограниченном режиме (только для фьючерсов Unpin ), это кажется предпочтительным. Поскольку я не пробовал, не могу сказать, так ли это сейчас. Я думаю, что еще нужно добавить как минимум Unpin границ вручную.

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

Добавление T: Unpin к mem::swap означало бы, что его нельзя использовать для определенных типов, даже если они не находятся внутри пина.

Но можно ли это исправить, добавив Unpin, привязанный к swap ()? Поскольку весь код до сих пор должен быть откреплен или в любом случае небезопасен, это не должно ничего сломать.

Это нарушит весь общий код: если вы напишете функцию в сегодняшней стабильной ржавчине для некоторого T без ограничений, вы можете вызвать для нее swap . Это должно продолжать работать.

Неподвижные типы работают очень хорошо, не привнося излишней сложности.

Никто не продемонстрировал способ добавления неподвижных типов в Rust обратно совместимым способом без сложности, значительно превышающей то, что предлагается здесь для стабилизации. Вы должны найти некоторые из этих старых предложений и обсуждений в репозитории RFC. См. Https://github.com/rust-lang/rfcs/pull/1858 для одного примера.

RFC на https://github.com/rust-lang/rfcs/pull/2349, а также серия блогов о лодке, начинающаяся здесь, должны помочь вам получить некоторую предысторию и некоторое представление о других рассматриваемых проектах. (Также обратите внимание на даты, этот дизайн находится в разработке почти 10 месяцев!)

Также mem :: swap - отвлекающий маневр, потому что это совсем не интересная функция. Это буквально просто

let temp = *a; 
*a = *b; 
*b = temp;

@Gankro не использует ли небезопасный код? Афаик, это невозможно написать буквально.

Изменить: я предполагаю, что другой способ думать об этом заключается в том, что добавление T: Unpin к mem::swap фактически меняет определение безопасности на уровне языка. Это сломало бы все mycrate::swap fns в дикой природе.

Добавление T: Unpin к mem :: swap означало бы, что его нельзя использовать для определенных типов, даже если они не находятся внутри Pin.

Если Unpin выводится автоматически (так же, как Sync / Send ), то я подумал, что это не должно быть проблемой.

Это нарушит весь общий код: если вы напишете функцию в сегодняшней стабильной ржавчине для некоторого T без ограничений, вы можете вызвать для нее swap. Это должно продолжать работать.

Но это очевидно. Не думал о том, что границы трейтов нужно явно передавать в Rust.

Никто не продемонстрировал способ добавления неподвижных типов в Rust обратно совместимым способом без сложности, значительно превышающей то, что предлагается здесь для стабилизации. Вы должны найти некоторые из этих старых предложений и обсуждений в репозитории RFC. См. Пример в rust-lang / rfcs # 1858.

Спасибо, я прочитаю немного больше о предыдущей работе над этим, если найду время. Очевидно, что на это уже было вложено много мыслей и усилий, и я определенно не хочу что-то блокировать. Я просто хотел поделиться своими опасениями и вопросами, когда пытаюсь с этим работать.

@ Matthias247

Если Unpin выводится автоматически (таким же образом, как и Sync / Send), я подумал, что это не должно быть проблемой.

Я не уверен, ясно ли я сказал. Чтобы уточнить, совершенно безопасно перемещать тип !Unpin и, таким образом, совершенно безопасно перемещать их по mem::swap . Небезопасно перемещать тип T: !Unpin после закрепления, то есть внутри Pin<P<T>> .

Например, в коде async / await вы можете перемещать фьючерсы, возвращаемые асинхронной функцией, сколько хотите. Вы перестаете перемещать их только после того, как pin_mut! их или поместите в Pin<Box<..>>> или еще что-то.

У меня есть несколько вопросов по этому поводу:

  1. Учитывая важность Pin<T> для async и генераторов, а также потенциальную ненадежность при взаимодействии с другими частями Rust (например, swap & replace ), имеет любые формальная проверка (например, с помощью @jhjourdan или @RalfJung) предложенного здесь варианта закрепления API?

  2. Дает ли этот API какие-либо новые гарантии в отношении абстрактной машинной / операционной семантики Rust? То есть, если мы забудем о варианте использования в качестве механизма поддержки для генераторов async & await /, можем ли мы поместить его в ящик в экосистеме, и он будет работать, учитывая текущие гарантии, которые мы дайте?

  3. Какие API-интерфейсы или добавления в систему типов становятся невозможными в результате стабилизации API-интерфейса закрепления? (этот вопрос является расширением 2.)

  4. Какие возможности предлагает стабилизируемый API с точки зрения его развития, язык предоставил тип &pin T для улучшения проекции поля и тому подобное (что не очень хорошо с предлагаемым API).

У меня есть разные заметки:

  1. Документация в стандартной библиотеке кажется довольно скудной. :(

  2. Я согласен с другими, что конструкция закрепления довольно сложна и утомительна.

  3. Пример Unmovable в документации кажется слишком сложным и включает unsafe ; это кажется неоптимальным. При постепенной инициализации, как это подробно описано в проекте RFC на языке (то есть улучшении NLL), вместо этого мы можем получить:

struct Unmovable<'a> {
    data: String,
    slice: &'a str,
}

let um: Unmovable<'_>;
um.data = "hello".to_string();
um.slice = &um.data; // OK! we borrow self-referentially.

drop(um); // ERROR! `um.slice` is borrowing `um.data` so you cannot move `um`.

// You won't be able to take a &mut reference to `um` so no `swap` problems.

Это подразумевает нулевую опасность, и пользователю довольно легко с этим справиться.

Кроме того, API-интерфейсы std не предоставляют безопасного способа закрепления объектов в стеке.
Это связано с тем, что нет возможности безопасно реализовать это с помощью API функции.

А как насчет такого API?

pub fn using_pin<T, R, F>(value: T, f: F) -> R
where F: for<'a> FnOnce(Pin<&'a mut T>) -> R {
    pin_mut!(value);    // Actual implementation inlines this but the point is this API is safe as long as pin_mut! is safe.
    f(value)
}

Я не слишком внимательно следил за развитием API-интерфейсов закрепления, поэтому об этом можно было упомянуть или объяснить в другом месте, и мне не удалось его найти, извиняюсь, если это так:

Тип Pinned - это ZST, который не реализует Unpin ; это позволяет вам
подавить автоматическую реализацию Unpin на стабильной, где !Unpin impls
пока не будет стабильной.

Есть ли где-нибудь объяснение того, почему !Unpin impls нельзя сделать стабильным?

Это безопасно, только если Foo (содержащий тип) не является repr (упакованным),
потому что это заставляет поля перемещаться, чтобы выровнять их.

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

Несмотря на всю тонкость, это действительно хороший API. Хорошая работа!

@Centril Ralf написал о закреплении в своем блоге .

Интерфейсы API контактов вообще не включали языковых изменений и полностью реализованы в стандартной библиотеке с использованием уже существующих языковых функций. Это не влияет на язык Rust и не исключает каких-либо других языковых функций.

Pin - это действительно просто умная реализация одной из самых ценных и классических функций Rust: возможность вводить инварианты в API, помечая часть API unsafe . Pin обертывает указатель, чтобы сделать одну операцию ( DerefMut ) небезопасной, требуя, чтобы люди, которые ее делают, поддерживали определенные инварианты (чтобы они не выходили за пределы ссылки) и позволяя другому коду предполагаем, что этого никогда не произойдет. Похожий, гораздо более старый пример того же трюка - String , что делает небезопасным помещать байты, отличные от UTF8, в строку, позволяя другому коду предполагать, что все данные в String являются UTF8.

Есть ли где-нибудь объяснение того, почему! Открепление импов не может быть стабильным?

Отрицательные импликации в настоящее время нестабильны и совершенно не связаны с этими API.

Отрицательные имплсы в настоящее время нестабильны.

🤦‍♂️ В этом есть смысл, надо было подумать об этом еще немного. Благодарю.

@alexcrichton Это отличный анализ этого API, мы должны попытаться сохранить его в каком-то месте лучше, чем какой-то комментарий, который будет потерян!

Некоторые комментарии:

as_mut : Вопрос: а как насчет "вредоносного" имплта DerefMut? Это безопасный способ
для вызова предоставленного пользователем DerefMut, который изначально создает & mut P :: Target,
предположительно позволяя ему также изменять его. Как это безопасно?

По сути, когда вы вызываете new_unchecked , вы даете обещание о реализациях Deref и DerefMut для этого типа.

set : данный пин

(и тот факт, что мы ничего не знаем о
Открепить), не должно ли это гарантировать, что P :: Target никогда не перемещается? Если мы можем
однако повторно инициализировать его с помощью другого P :: Target, разве это не должно быть небезопасно?
Или это как-то связано с деструкторами и прочим?

Это удаляет старое содержимое указателя и помещает туда новое содержимое. «Прикреплен» не означает «никогда не падал», это означает «никогда не перемещался, пока не упал». Так что сбросить его или перезаписать при вызове drop можно. Вызов drop имеет решающее значение, это гарантия сброса, о которой я упоминал выше.

Где вы видите здесь что-то движущееся?

map_unchecked : Вопрос :: какой здесь контрпример? Если бы это было безопасно, какой пример демонстрирует нарушение гарантий Pin?

Примером может быть начало с Pin<&&T> и использование этого метода для получения Pin<&T> . Закрепление не распространяется на ссылки.

get_ref : Одно "возможно, попалось" здесь - внутренняя изменчивость, что, если бы T был
RefCell?

Действительно, это ошибка, но, как вы заметили, не проблема с надежностью. Было бы неправильно иметь метод, который идет от Pin<RefCell<T>> до Pin<&[mut] T> . По сути, происходит то, что RefCell не распространяет закрепление, мы могли бы impl<T> Unpin for RefCell<T> .

проводилась ли какая-либо формальная проверка (например, @jhjourdan или @RalfJung) варианта API закрепления, который предлагается здесь?

Нет, не с нашей стороны. Сообщения в блоге, о которых я упоминал выше, содержат некоторые мысли о том, как я бы начал это формализовать, но на самом деле мы этого не сделали. Если вы дадите мне машину времени или заинтересованного аспиранта, мы это сделаем. ;)

Если мы забудем о варианте использования в качестве механизма поддержки для async и await / генераторов, можем ли мы поместить его в ящик в экосистеме, и он будет работать, учитывая текущие гарантии, которые мы даем?

Это намерение.

Какие API-интерфейсы или добавления в систему типов становятся невозможными в результате стабилизации API-интерфейса закрепления? (этот вопрос является расширением 2.)

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

Какие возможности предоставляет подлежащий стабилизации API с точки зрения развития языка и типа вывода T для улучшения проекции поля и т. Д. (Что не очень хорошо с предлагаемым API).

Я больше не уверен насчет &pin T , это не очень хорошо соответствует новому универсальному Pin<T> . Что касается обработки прогнозов, нам понадобится некоторый прием, чтобы сказать, что « Unpin и Drop не реализованы для этого типа», тогда мы могли бы сделать это безопасно с помощью макроса. Для дополнительной эргономики нам, вероятно, потребуется общая возможность «проецирования поля» в языке, которая также охватывала бы переход от &Cell<(A, B)> к &Cell<A> .

Есть ли где-нибудь объяснение того, почему! Открепление импов не может быть стабильным?

Отрицательные имплименты AFAIK имеют некоторые давние ограничения, и если вы добавите к ним общие границы, они часто не будут работать так, как вы думаете. (Общие границы иногда просто игнорируются или около того.)

Может быть, Chalk исправит все это, может быть, только некоторые из них, но в любом случае мы, вероятно, не захотим блокировать это на Chalk.

Означает ли упаковка, что поля можно перемещать динамически? Это немного страшно.

Это единственный надежный способ вызвать drop в упакованном поле, учитывая, что drop ожидает выровненную ссылку.

Уверены ли мы, что llvm никогда не сгенерирует код для перемещения полей при других обстоятельствах? Точно так же возможно, что закрепленные значения в стеке могут быть перемещены llvm?

Копирование байтов LLVM не должно вызывать проблем, поскольку оно не может изменить поведение программы. Речь идет о более высокоуровневой концепции «перемещения» данных, наблюдаемой в Rust (например, потому что указатели на данные больше не указывают на них). LLVM не может просто перемещать данные в другое место, на которое у нас могут быть указатели. Точно так же LLVM не может просто перемещать значения в стеке, адрес которых был занят.

Хорошо, спасибо @RalfJung , имеет смысл! Некоторые уточняющие вопросы ...

Когда вы говорите «никогда не перемещал, пока не упал», это означает, что Drop может быть вызван там, где &mut self переместился с адреса Pin<&mut Self> ? Деструкторы не могут полагаться на точность внутренних указателей, верно?

Когда я смотрел на set я беспокоился о том, как будет обрабатываться паника, но я думаю, что это не проблема, если немного углубиться в кодогенератор.

Когда вы говорите «никогда не перемещал, пока не упал», это означает, что Drop может быть вызван там, где &mut self переместился с адреса Pin<&mut Self> ? Деструкторы не могут полагаться на точность внутренних указателей, верно?

@alexcrichton, как я понимаю, это означает, что никогда не перемещался, пока не вернется Drop::drop . В противном случае некоторые варианты использования (как минимум навязчивые коллекции и выделенные стеком буферы DMA) станут невозможными.

Деструкторы могут полагаться на то, что что-то никогда не перемещалось, если они могут доказать, что ранее это было в Pin . Например, если конечный автомат может войти в состояние только через API, который требует его закрепления, деструктор может предположить, что он был закреплен до того, как был отброшен, если он находится в этом состоянии.

Код не может предполагать, что нелокальные деструкторы не перемещают элементы, но, очевидно, вы можете предположить, что ваши собственные деструкторы не перемещают вещи, потому что вы их пишете.

Когда вы говорите «никогда не перемещался, пока не сбросил», это означает, что Drop может быть вызван, если & mut self переместился с адреса Pin <& mut Self>? Деструкторы не могут полагаться на точность внутренних указателей, верно?

Я имею в виду, что данные никогда не будут перемещаться (в том смысле, что они никуда не денутся, в том числе не будут освобождены), пока не будет вызвано drop . И да, drop может полагаться на работу в закрепленном месте, даже если его тип не может этого выразить. drop должно занять Pin<&mut self> (для всех типов), но, увы, для этого уже поздно.

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

Это позволяет, например, создать навязчивый связанный список, в котором деструктор элемента отменяет его регистрацию, корректируя соседние указатели. Мы знаем, что память не исчезнет без вызова этого деструктора. (Элемент все еще может просочиться , и тогда он просто останется в этом связанном списке навсегда. Но в этом случае он останется действительной памятью, поэтому нет проблем с безопасностью.)

Я прочитал все, что смог найти о Pin , Unpin и обсуждениях, которые привели ко всему этому, и, хотя я _think_, теперь я понимаю, что происходит, есть также много тонкости в отношении того, что означают разные типы, а также контракта, которому должны следовать разработчики и пользователи. К сожалению, я вижу относительно небольшое обсуждение этого в документации для std::pin или, в частности, Pin и Unpin . В частности, мне бы хотелось увидеть часть содержания из этих комментариев:

включены в документы. В частности:

  • Эти Pin защищают только "на один уровень".
  • Этот Pin::new_unchecked накладывает ограничения на имп для Deref и DerefMut .
  • Взаимодействие между Pin и Drop .
  • Как !Unpin нельзя перемещать только после того, как он помещен в Pin .
  • Обоснование того, почему Pin<Box<T>>: Unpin where T: !Unpin . Это связано с указанным выше ограничением «на один уровень», но я думаю, что этот конкретный пример с надлежащим объяснением будет полезен читателям.

Мне тоже очень помогли контрпримеры @alexcrichton . Дать читателю представление о том, что может пойти не так с различными методами unsafe помимо прозы, я думаю, очень поможет (по крайней мере, это определенно помогло мне). В общем, из-за тонкости этого API я хотел бы, чтобы разделы безопасности различных небезопасных методов были расширены и, возможно, также упоминались в документации уровня модуля std::pin .

Я сам еще не очень много использовал этот API, поэтому доверяю техническому мнению, что сейчас он в хорошем состоянии. Однако я думаю, что имена Pin , Pinned и Unpin слишком похожи и невыразительны для очень разных типов / признаков и относительно сложного API, что затрудняет понимание (о чем свидетельствуют несколько комментариев в этой ветке).

Кажется, они следуют соглашению об именах для маркерных черт, поэтому я не могу жаловаться, но мне интересно, можем ли мы сделать компромисс между многословием и самоочевидными именами:

  • Pinned - сбивает с толку, потому что это то же слово, что и Pin . Учитывая, что он используется как PhantomData для дополнения структуры метаинформацией, которая в противном случае отсутствовала бы, как насчет PinnedData , PhantomPinned , PhantomSelfRef или даже DisableUnpin ? Что-то, что указывает на образец использования и / или эффект, который он будет иметь.
  • Unpin - Как в https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922, меня смущает имя Unpin , потому что "un-pin "можно понимать по-разному. Что-то вроде IgnorePin , PinNeutral может быть?

Обычно я терплю неудачу и сам нахожу хорошие альтернативные имена ...

PhantomPin и PinNeutral кажутся мне особенно хорошими именами.

Просто чтобы предоставить контрапункт, я нашел Unpin интуитивно понятным (как только я это понял). Pinned сложнее сохранить, если я не использовал его в собственном коде. Как насчет изменения Pinned на NotUnpin ?

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

  • Pin -> Pinned : когда вам дают Pin , это действительно обещание, что то, что вам дано, _ было закреплено_ и будет _ оставаться_ закрепленным навсегда. Я не слишком сильно отношусь к этому, так как вы _ можете_ также говорить о том, чтобы получить "значительный значок". Pinned<Box<T>> для меня просто читается лучше, особенно в том смысле, что закреплен только Box , а не то, что в нем содержится.
  • Unpin -> Repin : для других маркерных черт мы обычно говорим о том, что вы можете сделать с чем-то, имеющим эту маркерную черту. Вероятно, поэтому в первую очередь был выбран Unpin . Однако я думаю, что мы действительно хотим, чтобы читатель здесь убрал, что что-то, что является Unpin может быть закреплено, а затем повторно закреплено в другом месте без каких-либо последствий или рассмотрения. Мне также нравится предложение @ mark-im о PinNeutral , хотя оно несколько более подробное.
  • Pinned -> PermanentPin : Я не думаю, что Pinned - хорошее имя, потому что то, что содержит Pinned самом деле не закреплено .. Это просто не Unpin . PhantomPin имеет аналогичную проблему, поскольку он относится к Pin , когда Pin самом деле не то, к чему вы хотите добраться. NotUnpin имеет двойное отрицание, что затрудняет рассуждение. Предложение @Kimundi о PhantomSelfRef довольно близко, хотя я все еще думаю, что это немного "сложно", и оно связывает свойство "не может быть перемещено после закрепления" с одним экземпляром, где это так (когда вы есть ссылка на себя). Мое предложение можно также сформулировать как PermanentlyPinned ; Не знаю, какая форма менее плохая.

Я думаю, что Pinned конечном итоге должно быть NotX где X - это то, что Unpin конечном итоге получает имя. Единственная задача Pinned - сделать так, чтобы включающий тип не реализовывал Unpin . (Изменить: и если мы меняем Unpin на что-то другое, по-видимому, двойной отрицательный результат не является проблемой)

Repin для меня не имеет смысла, потому что «Этот тип можно закрепить где-нибудь еще» - это просто побочный эффект «Этот тип можно переместить из булавки».

@tikue В каком-то смысле я чувствую то же самое, но наоборот. Я думаю, что Unpin следует сформулировать отрицательно "это _не_ ограничено Pin ", тогда как Pinned следует сформулировать положительно "это _is_ ограничено Pin ". В основном, чтобы избежать двойного негатива. Но это деталь; Мне нравится идея о двойственности. Может быть: s/Unpin/TemporaryPin , s/Pinned/PermanentPin ?

РЕДАКТИРОВАТЬ: Да, я понимаю вашу точку зрения о том, что Repin является побочным эффектом Unpin . Я хотел сообщить тот факт, что Pin "неважно" для типа Unpin , что, по моему мнению, не очень хорошо подходит Unpin . Следовательно, предложение выше TemporaryPin .

@jonhoo Я думаю, что основная причина, по которой я предпочитаю обратное, заключается в том, что Pinned препятствует реализации трейта, так что это, в самом простом смысле, для меня истинный негатив.

Изменить: как насчет:

Unpin -> Escape
Pinned -> NoEscape

Интересно .. Пытаюсь посмотреть, как это впишется в документацию. Что-то вроде:

В общем, когда вам дается Pin<P> , это сопровождается гарантией того, что цель P не будет двигаться, пока она не будет отброшена. Исключение составляют случаи, когда целью P является Escape . Типы, отмеченные как Escape обещают, что они останутся действительными, даже если они будут перемещены (например, они не содержат внутренних ссылок на себя), и поэтому им разрешено «экранировать» Pin . Escape - это автоматическая характеристика, поэтому все типы, которые полностью состоят из типов Escape , сами также являются Escape . Разработчики могут отказаться от этого в ночное время, используя impl !Escape for T {} или добавив маркер NoEscape из std::phantom .

Это кажется довольно приличным, хотя связь со словом «побег» кажется немного слабой. Кроме того, написание вышеупомянутого также заставило меня понять, что Pin<P> не _ действительно_ гарантирует, что цель P не будет перемещена (именно из-за Unpin ). Вместо этого он гарантирует, что _ либо__ не имеет значения, перемещается ли цель P _или_ цель P не будет перемещена. Не знаю, как использовать это, чтобы сообщить лучший выбор имен ... Но, вероятно, это то, что должно так или иначе попасть в документацию.

Лично мне тоже очень не нравится Unpin в качестве имени, возможно, потому, что оно чаще всего встречается как impl !Unpin которое читается как "не снимать" и требует нескольких мозговых циклов (я я более старая модель), чтобы сделать вывод, что это означает «хорошо, этот будет закреплен навсегда, как только он будет закреплен в первый раз», поэтому я даже не могу оптимизировать двойной отрицательный результат.
В общем, людям, как правило, труднее думать негативно, а не позитивно (без прямого источника, но, если сомневаетесь, ознакомьтесь с работой Ричарда Хадсона).
Кстати, Repin звучит очень приятно для меня.

Pin трудно объяснить, потому что он не всегда делает закрепленное значение неподвижным. Pinned сбивает с толку, потому что на самом деле ничего не закрепляет. Это просто предотвращает экранирование Pin .

Pin<P<T>> можно объяснить как закрепленное значение: закрепленные значения не могут перемещаться, если значение не является типом, который может выходить за пределы булавки.

Некоторый быстрый поиск в Google, кажется, показывает, что в борьбе, когда кто-то застрял, вырывание булавки называется побегом .

Мне также нравится термин escape вместо Unpin но я бы выбрал EscapePin .

Здесь много хороших мыслей!

Одно общее: для меня, как для иностранца, Pin и Unpin в основном являются глаголами / действиями. Хотя Pin имеет смысл, поскольку объект привязан к одной ячейке памяти за раз, я не могу увидеть то же самое для Unpin . Как только я получаю ссылку Pin<&mut T> , T всегда будет закреплен в том смысле, что его расположение в памяти стабильно, независимо от того, Unpin или нет. Невозможно открепить объект как действие. Разница в том, что для типов Unpin не требуются гарантии закрепления для дальнейшего взаимодействия. Они не ссылаются на себя, и их адрес в памяти, например, не отправляется другому объекту и не сохраняется там.

Я согласен с @tikue, что было бы неплохо получить потрясающую работу о том, что Unpin самом деле означает StableOnMoveAfterPin или просто StableMove может быть вариантом, но это тоже звучит не очень хорошо.

Repin для меня имеет те же сложности, что и Unpin , а именно: это означает, что сначала открепляется одна вещь, чего, в моем понимании, не происходит.

Поскольку эта черта в основном определяет, что происходит после того, как кто-то видит Pin этого типа, я считаю, что такие вещи, как PinNeutral или PinInvariant не так уж плохи.

Что касается Pin против Pinned , я бы предпочел Pinned , поскольку это состояние указателя в момент времени, когда его видят.

@ Matthias247 Я не думаю, что вам гарантировано, что адрес P::Target стабилен, если P: Unpin ? Я мог ошибаться в этом?

@ Matthias247

Как только я получаю ссылку на Pin <& mut T>, T всегда будет закреплен в том смысле, что его расположение в памяти стабильно, независимо от того, открепить он или нет.

Вы можете пояснить, что вы имеете в виду? Учитывая T: Unpin вы можете написать следующее:

let pin_t: Pin<&mut T> = ...
let mut other_t: T = ...
mem::replace(Pin::get_mut(pin_t), &mut other_t);
// Now the value originally behind pin_t is in other_t

@jonhoo На самом деле хороший вопрос. Я считал, что фьючерсы упаковываются, и тогда их метод poll() будет вызываться по тому же адресу памяти. Но это, очевидно, относится только к задаче / будущему верхнего уровня, а промежуточные уровни могут перемещать фьючерсы, когда они равны Unpin . Так что, похоже, вы правы.

По последним данным велосипедной раздачи:

  • Unpin : а как насчет MoveFromPin ? Если я все еще не упускаю некоторые тонкости, я думаю, это прямо указывает на то, какие возможности на самом деле позволяет трейт: если тип находится в пределах Pin , вы все равно можете его переместить.

    Важно то, что он как бы говорит то же самое, что и Unpin , но оформлен как положительное утверждение, поэтому вместо дважды отрицательного !Unpin теперь у нас есть !MoveFromPin . Я думаю, что мне легче интерпретировать, по крайней мере ... это э-э, типы, которые невозможно вывести из булавки.

    (Есть место для вариаций основной идеи: MoveOutOfPin , MoveFromPinned , MoveWhenPinned и так далее.)

  • Pinned : затем это может стать NoMoveFromPin , и в результате будет получен тип !MoveFromPin . Я думаю, это кажется достаточно простым.

  • Сама по себе Pin : эта не связана с двумя другими и тоже не так значительна, но я думаю, здесь тоже есть место для небольшого улучшения.

    Проблема в том, что Pin<&mut T> (например) не означает, что &mut закреплен, это означает, что T (например, путаница, которую я видел на хотя бы один недавний комментарий). Поскольку часть Pin действует как своего рода модификатор для &mut , я думаю, что в некоторых случаях было бы лучше назвать ее Pinning .

    Для этого есть некоторый косвенный прецедент: если мы хотим изменить семантику переполнения целочисленного типа, чтобы вместо паники обернуться, мы говорим Wrapping<i32> а не Wrap<i32> .

Все они длиннее оригиналов, но, учитывая, насколько деликатны некоторые аргументы вокруг них, это может быть стоящим вложением для большей ясности.

Re: Repin , я ожидал, что это будет что-то вроде

unsafe trait Repin {
    unsafe fn repin(from: *mut Self, to: *mut Self);
}

который можно использовать для поддержки типов !Unpin внутри Vec -подобной коллекции, которая иногда перемещает свое содержимое (это не предложение о добавлении такой черты сейчас или когда-либо, просто мое первое впечатление от имя черты).

Также байкшединг имен:

  • Pin<P> -> Pinned<P> : значение, на которое указывает указатель P , закрепляется в памяти на время его существования (до тех пор, пока не будет удалено).
  • Unpin -> Moveable : значение не нужно закреплять и его можно свободно перемещать.
  • Pinned (struct) -> Unmoveable : должен быть Pinned и не может быть перемещен.

Я не думаю, что Pin или Unpin должны меняться, все альтернативы добавляют многословия без ясности, на мой взгляд, или даже вводят в заблуждение. Также у нас уже был этот разговор, мы приняли решение использовать Pin и Unpin , и ни один из аргументов, приведенных в этой ветке, не является новым.

Однако со времени предыдущего обсуждения был добавлен Pinned , и я думаю, имеет смысл сделать его PhantomPinned чтобы очистить его от фантомного типа маркера, например PhantomData .

Лично мне тоже очень не нравится Unpin как имя, возможно, потому, что оно чаще всего рассматривается как impl! Unpin, которое читается как «not-un-pin» и требует нескольких мозговых циклов (я старая модель), чтобы сделать вывод что это означает «хорошо, этот будет закреплен навсегда, как только он будет закреплен в первый раз», поэтому я даже не могу оптимизировать двойной негатив.

Это полностью противоположно моему опыту: ручная реализация !Unpin означает, что вы реализуете самореферентную структуру вручную с использованием небезопасного кода, что является крайне узкоспециализированным вариантом использования. Напротив, все, что сохраняет потенциально открепленную структуру за указателем, имеет положительное имплицирование Unpin . Например, все импликации Unpin в std имеют положительную полярность.

@withoutboats не могли бы вы предоставить ссылку на предыдущее обсуждение этих имен, где приведенные здесь аргументы уже обсуждались?

Вот один поток, хотя он, безусловно, также обсуждался в потоке RFC и проблеме отслеживания https://internals.rust-lang.org/t/naming-pin-anchor-move/6864

(типы в этом потоке, называемые якорем и булавкой, теперь называются Pin<Box<T>> и Pin<&'a mut T> )

Следует ли понимать «Unpin» как сокращение от «Unpinnable»? Невозможно закрепить, так как на самом деле вы не можете сохранить значение закрепленным, даже если оно находится внутри булавки. (Это вообще правильное понимание с моей стороны?)

Я просмотрел некоторые документы и темы комментариев и не увидел никаких ссылок на Unpinnable.

«Открепить» не должно быть сокращением. Одна вещь, которая, как мне кажется, не очевидна для многих пользователей, но это правда: руководство по стилю стандартной библиотеки предпочитает глаголы в качестве имен признаков, а не прилагательные - следовательно, Send , а не Sendable . Это не было применено с идеальной последовательностью, но это норма. Unpin означает «открепить», так как этот тип можно открепить от Pin вы его закрепили.

Такие имена, как Move (не "перемещаемый", помните), менее ясны, чем Unpin потому что они подразумевают, что это связано с возможностью его вообще перемещать, а не связывать поведение с тип штифта. Вы можете перемещать типы !Unpin , потому что вы можете перемещать любое значение Sized в Rust.

Имена, состоящие из целых фраз, как я заметил, были бы очень унидиоматичными для std.

Возможно, это не сокращение, но я именно так его прочитал. Поскольку черты являются глаголами, вам нужно вручную преобразовать его в прилагательное, если вы хотите использовать его для описания типа, а не операции над типом; std::iter::Iterator реализуется чем-то, что можно повторять, std::io::Seek реализуется чем-то, что доступно для поиска, std::pin::Unpin реализуется чем-то, что не может быть найдено.

@withoutboats - какая-то конкретная проблема с некоторыми другими именами, в которых нет глагола, например Escape или EscapePin ? Понимаю, что это обсуждение происходило раньше, но, по-видимому, сейчас на это обращает больше внимания, поэтому я не уверен, что это полностью повторяющийся перефраз ...

Одна вещь, которую я считаю верной, заключается в том, что, к сожалению, Pin и Unpin могут быть прочитаны как пара (некоторые типы являются "закрепленными", а некоторые - "открепленными"), когда Pin должно быть существительным , а не глаголом . Мы надеемся, что тот факт, что Pin не является чертой, проясняет ситуацию. Аргумент в пользу Pinning имеет смысл, но здесь мы сталкиваемся с проблемой длины имени. Тем более, что получателям методов придется дважды повторять self , в итоге получается много символов: self: Pinning<&mut Self> . Не уверен, что Pinning<P> - это целых четыре символа, более понятных, чем Pin<P> .

@tikue, я думаю, терминология "побега" гораздо более перегружена, чем закрепление, что противоречит таким концепциям, как анализ побега.

Кроме того, у нас уже был этот разговор, мы пришли к решению использовать Pin и Unpin, и ни один из аргументов, приведенных в этой теме, не является новым.

Это меня неправильно расстраивает - нежелательны ли сообщения об опыте сообщества? Я лично не видел явной проблемы с Unpin до некоторых других комментариев в этой ветке, а также сопоставления с двойным отрицанием Pinned .

Rust не выполняет анализ выхода, поэтому я не уверен, что вижу в этом реальную проблему.

: bell: Сейчас наступает последний период для комментариев , как указано в обзоре выше . : колокол:

Я все еще хотел бы увидеть улучшения документации, как описано в https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891, прежде чем это произойдет :)

Я открыл https://github.com/rust-lang/rust/pull/55992, чтобы добавить документацию, предложенную выше, и переименовать Pinned в PhantomPinned .

Я думаю, что PinnedPhantomPinned ) поощряет концептуализацию "закрепленного" значения как такого, которое не может выйти из Pin , что означает, что многие значения в Pin (те, чьи типы подразумевают Unpin ) не "закреплены"!

Это сбивает с толку. Мне легче представить все значения в Pin как закрепленные, пока они находятся в Pin , и то, является ли закрепление постоянным или нет, - это то, что раньше называлось Pinned controls. Имя, отличное от Pin* , предотвратит слияние двух различных концепций.

PhantomNotUnpin : P

Лично мне тоже очень не нравится Unpin как имя, возможно, потому, что оно чаще всего рассматривается как impl! Unpin, которое читается как «not-un-pin» и требует нескольких мозговых циклов

Благодаря! Я также был обеспокоен Unpin течение довольно долгого времени, бот не мог точно определить (хех), почему. Теперь я думаю, что понимаю: это двойное отрицание.

Это полностью противоположно моему опыту, реализация вручную! Открепить означает, что вы реализуете самореферентную структуру вручную с использованием небезопасного кода, что является чрезвычайно узкоспециализированным вариантом использования. Напротив, все, что сохраняет потенциально открепленную структуру за указателем, имеет положительный имплицит Unpin. Например, все импликации Unpin в std имеют положительную полярность.

Но дело не только в реализации, но и в обсуждении. impl !Sync встречается довольно редко (не только потому, что он нестабилен), но разговоры о типах Sync и !Sync довольно распространены. Точно так же !Unpin довольно часто упоминался в обсуждениях этой функции, по крайней мере, в тех, которые у меня были.

Я тоже предпочел бы что-то, что положительно выражает свойство ( MoveFromPin или около того). Эргономика меня не совсем убеждает, поскольку, в отличие от Pin не нужно так часто писать привязку этой черты.

Rust не выполняет анализ выхода, поэтому я не уверен, что вижу в этом реальную проблему.

LLVM это делает, поэтому анализ escape-кода по-прежнему актуален для Rust.

Чтобы решить эту проблему, выберите слова, которые меняют значение Pin / Unpin . Например, переименуйте Unpin в Relocate . Тогда !Unpin становится !Relocate . Для меня это имеет гораздо более интуитивный смысл - я прочитал это как «О, объекты этого типа не могут быть перемещены». Другой претендент - Movable .

Я не уверен, какое противоположное слово могло бы заменить Pin , или если нам это вообще нужно. Но я, конечно, мог представить себе, что документы говорят что-то вроде этого:

Закрепленный объект может быть изменен напрямую через DerefMut тогда и только тогда, когда объект может быть перемещен в памяти. Relocate - автоматическая черта - добавляется по умолчанию. Но если будут прямые указатели на память, содержащую ваши значения, откажитесь от Relocate , добавив impl !Relocate к вашему типу.

impl<T: Relocate> DerefMut for Pin<T> { ... }

Для меня это имеет гораздо более интуитивный смысл, чем Unpin .

Я открыл # 55992, чтобы добавить документацию, предложенную выше

Однако это добавляет лишь часть того, что было предложено в https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891.

Мне нравится предложение MoveFromPin . Relocate тоже хорошо, но, возможно, недостаточно связано с Pin . Его снова можно было понять как неподвижный тип (а это не так). RelocateFromPin снова было бы хорошо.

Escape ing - это то, что также, например, связано в Swift с замыканиями и независимо от того, вызываются ли они внутри или вне текущей цепочки вызовов. Звучит обманчиво.

Я не вижу проблем с более длинными именами, если это помогает ясности.

FWIW Я также хотел бы провести голосование, чтобы переименовать Unpin во что-то "положительное", например Relocate или MoveFromPin (или даже более подробное, но, возможно, более точное MayMoveFromPin ).

Я согласен с тем, что двойной отрицательный результат !Unpin или просто Unpin исторически сбивал меня с толку, и что-то, оформленное в положительную форму, «это может двигаться, несмотря на то, что находится внутри Pin », я думаю помогло бы облегчить некоторую путаницу!

FWIW Сначала я думал то же самое о Unpin , но когда я действительно пошел использовать его, IMO, это имело смысл - это не совсем двойное отрицание, поскольку операция, которую вы ищете, - это возможность Unpin (брать что-то в Pin бесплатно), а не способность удерживать вещи в булавке. Это то же самое, что и MoveFromPin , только сформулировано иначе. Я бы предпочел имя, которое не заставляет людей думать, что это «не Pin » или что-то в этом роде, но IMO MoveFromPin и другие слишком многословны. UndoPin ? ( FreePin для толпы haskell?)

Я все еще думаю, что !Unpin звучит странно - я научил свой внутренний голос относиться к нему больше как «Не открепляйте это!» вместо обычного «Не реализует Unpin », но потребовались некоторые усилия.

А как насчет !Pluck ?

@runiq

вместо привычного "Открепить не реализует"

Я упоминал об этом в своем предыдущем комментарии, но я думаю, что это действительно хороший способ сказать это: Unpin - это операция по входу и выходу из Pin<C<_>> . То, что не реализует Unpin , не дает такой возможности.

@cramertj Мне очень нравится UndoPin

@cramertj Я согласен с тем, что я не большой поклонник предлагаемых альтернатив, но я лично предпочел бы многословный MoveFromPin Unpin . Это хорошо, что это не двойное отрицание, но при чтении его (как человека, который еще не работал с этим тонну) меня продолжает сбивать с толку, что это двойной отрицательный результат. Я все время пытаюсь прочитать приставку "Un" как отрицательную ...

Мне любопытно, но @cramertj или другие, как вы думаете, есть хорошая информация о том, насколько эргономично возникает Unpin ? Это супер редкость? Это очень часто? Достаточно часто, чтобы быть болью, если это больно печатать?

Для короткого и приятного имени мне лично нравится идея Relocate , но для более длинного и многословного-но-хорошо-потому что-ты-не-набираешь-столько-то мне нравится MoveFromPin . Я лично считаю, что независимо от эргономики набора имени оба варианта лучше, чем Unpin

Мне любопытно, но @cramertj или другие, как вы думаете, есть хорошее представление о том, насколько эргономично возникает

По моему опыту, есть два случая, когда Unpin действительно появляется в пользовательском коде:

  1. Unpin границы возникают, потому что вам действительно нужно перемещать будущее, когда вы его опрашиваете (например, такие вещи, как некоторые избранные API).
  2. Чаще всего, если ваше будущее является общим только для чего-то другого, кроме будущего, вы обычно хотите добавить безусловную реализацию Unpin , потому что не имеет значения, являются ли эти другие типы Unpin поскольку вы никогда не прикрепить к ним проект.

Примером второго случая является случай, когда у вас есть общий тип буфера (например, T: AsRef<[u8]> ). Вам не нужно закреплять его, чтобы получить срез, поэтому вам все равно, реализует он Unpin или нет, поэтому вы просто хотите сказать, что ваш тип безоговорочно реализует Unpin чтобы вы могли реализовать Future игнорирование закрепления.

Unpin довольно часто рассматривается как привязка - select! , StreamExt::next и другие комбинаторы требуют, чтобы типы, с которыми они работают, были Unpin .

@withoutboats Мне любопытно ваше impl Unpin - это то, что людям придется часто помнить о реализации? Подобно тому, как сегодня авторы библиотек часто забывают #[derive(Debug)] или impl std::error::Error для своих пользовательских типов, что затрудняет использование этих библиотек?

Открепление - это автоматическая черта. Единственный случай, когда у вас будет тип, который не реализует unpin, - это когда этот тип явно откажется от него. (Или содержит поле, которое нельзя открепить).

Я понимаю, что это так. И я предположил, что это, безусловно, самый распространенный случай, поэтому я был удивлен, что @withoutboats даже упомянул второй пункт. Это наводит на мысль, что это может быть более распространенным явлением, чем я первоначально думал (хотя, возможно, только при реализации ваших собственных фьючерсов), поэтому мне любопытно, как часто встречаются такие варианты использования :)

@alexcrichton Мне любопытно, но @cramertj или другие, как вы думаете, есть хорошая подсказка о том, насколько эргономично подходит

Я написал длинный пост о моем опыте переноса кода на Futures 0.3 (который использует Pin ). Вам не нужно его читать, краткое содержание:

В большинстве случаев вам вообще не нужно беспокоиться о Unpin , потому что Unpin автоматически реализуется почти для всех типов.

Итак, единственный раз, когда вам нужно беспокоиться о Unpin это:

  1. У вас есть тип, который является общим по сравнению с другими типами (например, struct Foo<A> ).

  2. И вы хотите реализовать API закрепления (например, Future / Stream / Signal ) для этого типа.

В этом случае вам нужно использовать это:

impl<A> Unpin for Foo<A> where A: Unpin {}

Или это:

impl<A> Unpin for Foo<A> {}

impl<A> Future for Foo<A> where A: Unpin { ... }

Обычно это единственная ситуация, когда требуется Unpin . Как видите, обычно это означает необходимость использования Unpin ~ 2 раза для каждого типа.

В случае некомбинаторов или в случае типов, которые не реализуют Future / Stream / Signal , вам не нужно использовать Unpin вообще.

Итак, я бы сказал, что Unpin появляется очень редко, и только в ситуации создания комбинаторов Future / Stream / Signal .

Так что я решительно поддерживаю такое имя, как MoveFromPin . Закрепление - это нишевая функция, с которой большинству людей никогда не приходится иметь дело, поэтому не следует чрезмерно оптимизировать длину имени.

Я думаю, что когнитивные преимущества (предотвращение двойного отрицания) намного важнее, чем сохранение нескольких персонажей в редких ситуациях.

Тем более, что закрепление уже достаточно сложно для понимания! Так что давайте не будем усложнять задачу излишне.

@jonhoo, думаем ли мы, что impl Unpin - это то, что людям придется часто помнить о реализации? Подобно тому, как сегодня авторы библиотек часто забывают #[derive(Debug)] или impl std::error::Error для своих пользовательских типов, что затрудняет использование этих библиотек?

Я не думаю, что можно забыть impl Unpin , потому что, если автор забудет, он получит ошибку компилятора, которая не позволяет опубликовать его ящик. Так что это совсем не похоже на #[derive(Debug)] .

Ситуация, о которой говорит комбинаторы Future / Stream / Signal , она ни на кого не влияет (в частности, не влияет нижестоящие пользователи библиотеки).

(Я думаю, что двойное отрицание является частью этого, и, возможно, просто больше отрицания, чем строго необходимо, но я чувствую, что это еще не вся история (пытаюсь самоанализ) ... "Открепить" - это немного , метафорический? Косвенный? Это имеет смысл при объяснении, и поскольку «закрепление» само по себе уже является метафорой, непонятно, почему у моего мозга возникнут проблемы с дальнейшим развитием этой метафоры, но, тем не менее, мой мозг находит значение "открепить" по какой-то непонятной причине, за которую трудно закрепиться.)

Думаю, вы правы, @cramertj , на самом деле это не двойное отрицание в обычном смысле слова, но, как и @alexcrichton и @glaebhoerl, меня это все время это отрицание прижав здесь, если только в качестве глагола.

@withoutboats Мне любопытно ваше

Точно нет! Если пользователь вручную реализует Future который является универсальным для не-будущего, он, вероятно, захочет иметь возможность изменять состояние в своей будущей реализации без написания небезопасного кода, то есть лечить Pin<&mut Self> как &mut self . Они получат ошибки, указывающие, что MyFuture<T: AsRef<[u8]>> не реализует Unpin . Лучшее решение этой проблемы - реализовать Unpin . Но единственное влияние это оказывает на пользователя, пытающегося реализовать Future , и его невозможно забыть, потому что его код не будет компилироваться.

Ситуация, о которой говорит

Я специально говорю о некомбинаторных универсальных ручных фьючерсах , которые должны иметь только общие имплименты Unpin даже если их дженерики не равны Unpin .

Я сделал блок-схему для вопроса «Что мне делать с закреплением, когда я реализую ручное будущее / поток?»

pinning-flowchart

Чтобы побольше велосипеда, сегодня за обедом я придумал LeavePin . Он несет тот же тон, что и escape без подразумеваемой вводящей в заблуждение семантики.

Есть ли какие-нибудь интересные взаимодействия между специализацией impl и пинами, как и время жизни?

Подстрока «specializ» немного встречается при отслеживании обсуждения проблемы , но не окончательно. Pin RFC или RFC специализации должны явно упоминать это взаимодействие, либо как «Подтверждено, что все в порядке», либо «необходимы дальнейшие исследования, чтобы судить о безопасности».

@vi не только не существует не разумность взаимодействия, его не возможно там быть взаимодействие разумность. API контактов строго определены в стандартной библиотеке и не содержат новых языковых функций, пользователь может так же легко определять их в сторонних библиотеках. Если языковая функция не работает перед лицом существующего библиотечного кода, это ненадежный период , потому что любой пользователь может написать этот библиотечный код сегодня, и он будет компилироваться нормально.

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

Не то чтобы это должно иметь какое-либо отношение к pin ... но я не думаю, что это так просто.

Если функция библиотеки при использовании unsafe использует языковые конструкции, поведение которых еще не определено (например, &packed.field as *const _ , или различные предположения о ABI), то если дополнительные языковые изменения делают предположения недействительными Из этих библиотек я думаю, что это библиотеки, которые не работают, а не изменения языка. С другой стороны, если изменения языка делают определенное поведение необоснованным, то это вина изменений языка. Таким образом, компилирование не является достаточным условием для работоспособности библиотеки перед лицом небезопасных и языковых изменений.

+1 к MoveFromPin или аналогичному

Если вы зададите вопрос «Когда мне следует отменить Unpin для моего собственного типа?», Ответ будет гораздо более ясным, если вместо этого вы спросите: «Когда мне следует отменить реализацию MoveFromPin для моего собственного типа?»

То же самое с «Следует ли мне добавить здесь привязку« Открепить »?» vs "следует ли мне добавить MoveFromPin в качестве привязки сюда?"

Закрепления нет!

Извините, если это было где-то упомянуто, но я только бегло просмотрел огромное количество обсуждений вокруг Pin здесь, в вопросе реализации и в вопросе RFC.

Будет ли ржавчина когда-нибудь! Я определенно вижу варианты использования такой вещи (черт возьми, я пришел посмотреть на Пина, потому что искал способ не выстрелить себе в ногу с типами, которые нельзя двигать). Если да, то как это будет взаимодействовать с Пином? Будет ли из-за наличия Pin сложнее добавить! Move?

Последний период комментариев с предложением о слиянии в соответствии с приведенным выше обзором завершен .

Должно возникнуть нерешенное беспокойство по поводу наименования Unpin.

Как отметил @RalfJung , # 55992 также добавляет небольшую часть дополнительной документации, запрошенной на https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891 и в других местах. Но не знаю, что это основание для отказа от слияния.

Почему мой метод drop () теперь снова принимает & mut self вместо Pin.

Что ж, drop () старый - он существует с Rust 1.0, поэтому мы не можем его изменить. Мы бы хотели, чтобы он принимал Pin <& mut Self>, а затем типы Unpin могли бы получать свои & mut, как сейчас, но это изменение не имеет обратной совместимости.

Мне было интересно, можно ли реализовать это изменение обратно совместимым способом. AIUI, пока мы не добавим Unpin (и люди могут указать !Unpin ), все типы реализуют Unpin . Итак, мы можем добавить черту:

trait DropPinned {
    fn drop(Pin<&mut> self);
}

а затем примените эту черту для всех типов Unpin , которые, пока люди не смогут отказаться, относятся ко всем типам. Что-то вроде:

impl<T> PinDrop for T where T:Unpin + Drop {
    fn drop(Pin<&mut T> self) {
        Drop::drop(self.get_mut());
    }
}

Тогда у нас будет компилятор вставлять вызовы в DropPinned::drop вместо Drop::drop . По сути, трейт DropPinned становится lang-item, а не Drop . AFAICS, это было бы обратно совместимо, если и только если бы этот механизм был введен одновременно с Unpin .

Должно возникнуть нерешенное беспокойство по поводу наименования Unpin.

@tikue Ни один из членов команды libs не подал жалобу на rfcbot до или во время FCP, и я не верю, что какие-либо аргументы о Unpin были новыми или новыми для этого потока, поэтому обычно наш процесс будет учитывать текущий название будет уточнено. Очевидно, что если у кого-то есть проблемы, они должны сообщить об этом, но смысл наличия командных флажков, за которыми следует FCP, заключается в том, чтобы гарантировать, что всем комфортно стабилизировать API, как предлагается.

@cramertj Я немного запуталась. Некоторые люди говорили до. Когда я попросил ссылки на то, где еще были подняты и разрешены споры по поводу наименования Unpin , меня указали на https://internals.rust-lang.org/t/naming-pin-anchor- ход / 6864 , который, насколько я могу видеть , также есть люди , жалующиеся о наименовании Unpin , и никакого реального контраргумент. Исходный RFC в https://github.com/rust-lang/rfcs/pull/2349 также не имеет большого объяснения того, почему предлагаемые альтернативы Unpin хуже. Даже в этой беседе кажется, что единственные действительно возникающие контраргументы - это «это короче» и «это технически правильно». Можете ли вы указать на конкретное обсуждение, в котором альтернативные имена, которые легче понять (например, MoveFromPin ), обсуждаются и отклоняются?

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

@tikue Я поднимал и видел дважды отрицательную проблему, Unpin поднималось как проблема много раз и постоянно решалось в пользу Unpin . Как я уже говорил ранее, если кто-то из команды libs хочет зарегистрировать (позднее) возражение, я могу ответить на это, но все они подписались с предложением стабилизации выше, которое явно не включает именование Unpin как нерешенный вопрос: мы обсудили альтернативы, и этот FCP был процессом для принятия решения о том, что мы готовы к стабилизации в отношении принятых решений, включая имя Unpin .

@cramertj Не могли бы вы дать ссылку на то, где происходило это обсуждение? Я не сомневаюсь в вас, просто хотел бы увидеть аргументы в пользу Unpin , потому что я не верю, что они были здесь приведены. Как уже упоминалось, ссылки, которые я давал до сих пор, не дают никакого разрешения по поводу наименования Unpin .

@cramertj +1 к вопросу @jonhoo . Если есть обсуждения между командой libs, которые не зарегистрированы в официальных каналах, я думаю, что основной смысл этих обсуждений следует повторить здесь. Я думаю, есть даже официальное правило, что решения RFC могут приниматься только на основе общеизвестных аргументов?

Я думаю, есть даже официальное правило, что решения RFC могут приниматься только на основе общеизвестных аргументов?

Да, это правда - и для протокола, я не в команде libs и поэтому не присутствовал ни на каких обсуждениях только libs-team. Просматривая RFC-поток и проблему отслеживания Pin , было много раз, когда упоминалось имя Unpin . Я не вижу, чтобы кто-то специально произносил слова «двойное отрицание», но я точно помню, что « !Unpin - это двойное отрицание» упоминалось ранее, в дополнение к общему правилу API именования признаков для того, что вы можете делать с ними, а не то, что вы не можете (как я указал выше, я думаю, что Unpin самом деле следует обоим этим правилам, хотя понимая, что для этого нужно слышать «Открепить» как глагол, а не как прилагательное "не-булавка", которая не понятна людям).

@wmanley Это не работает, например, impl<T> Drop for Vec<T> сломается, потому что у нас нет Vec<T>: Unpin . Также предложения в этом направлении высказывались и раньше, даже в этой ветке . Пожалуйста, прочтите обсуждения, прежде чем отвечать. Я знаю, что это довольно много вопросов, но это единственный способ избежать объяснения одних и тех же проблем снова и снова.

Я думаю, есть даже официальное правило, что решения RFC могут приниматься только на основе общеизвестных аргументов?

Это неофициально известно как правило «без нового обоснования» .

Не уверен, где это разместить, но может ли кто-нибудь взглянуть на https://github.com/rust-lang/rust/issues/56256 ?

Относительно # 56256, impl<T> From<Box<T>> for Pin<Box<T>> не указан в OP как стабилизируемый, но он неявно станет стабильным, как только Pin сделает это. Существуют ли другие нетривиальные реализации трейтов, которые следует искать для стабилизации? (сканирование документов, все остальные кажутся тривиальными реализациями делегирования мне обернутого указателя).

Мы говорили об этой проблеме сегодня в команде libs. Последние недели обсуждения показали, что есть еще несколько вещей, которые следует решить в первую очередь, особенно в отношении наименования Unpin .

Поэтому мы пока не будем продвигаться к стабилизации этого положения (несмотря на FCP).

Мы были бы очень признательны, если бы кто-нибудь мог собрать идеи в этой ветке и подготовить отдельное предложение по улучшению ситуации с именами.

@Kimundi

Мы были бы очень признательны, если бы кто-нибудь мог собрать идеи в этой ветке и подготовить отдельное предложение по улучшению ситуации с именами.

Означает ли это, что команда libs не желает стабилизировать текущие API как есть? Лично я не думаю, что кто-то придумал имя лучше, чем текущий набор имен в том виде, в каком он реализован, поэтому я не могу сделать такое предложение, но меня очень волнует стабилизация этих API, поэтому, если у кого-то из команды libs есть имя, которое они предпочли бы, тогда: shipit:

Bikeshedded это с кучей коллег, и @anp предложил DePin , что мне на самом деле очень нравится, так как он удаляет коннотацию "не закреплен" в Unpin и подчеркивает, что это говорит о типе что может быть де- Pin 'd.

@Kimundi, не могли бы вы или кто-нибудь из команды libs зарегистрировать явную "озабоченность fcp", чтобы убрать это из FCP и более четко изложить, что имеется в виду под "несколькими вещами, которые необходимо решить"?

@rfcbot касается именования

Я не уверен, действительно ли это работает после входа в FCP, но я хотел бы выразить формальную озабоченность по поводу блокировки именования признака Unpin . Признак Unpin выглядит очень важным для API, а «двойной отрицательный» (как обсуждалось выше) заставляет меня нервничать каждый раз, когда я его читаю.

Было много комментариев по поводу различных имен, но, к сожалению, я пока не особо в восторге от них. Мой «фаворит» по-прежнему примерно равен MoveFromPin или Relocate (боже мой, здесь столько комментариев, что я понятия не имею, как это просмотреть).

Я лично согласен с именованием самого Pin , а также Pinned для ZST, который не реализует признак Unpin .

Я полностью согласен с @alexcrichton в том, что название Unpin является здесь предметом спора. Я не думаю, что есть какие-либо технические проблемы, насколько я могу судить по поводу самой предлагаемой функции (хотя есть _ много комментариев, так что мог что-то пропустить).

Я все еще думаю, что Pinned - странное имя для ZST, потому что то, что содержит Pinned самом деле не закреплено ... Это просто не Unpin . PhantomPinned (поскольку он был переименован в # 55992) имеет ту же проблему, поскольку он относится к Pin , когда ZST _ действительно_ около Unpin .

Я также по- прежнему считаю , что документы нужно больше работать учитывая тонкость этой функции, но это , вероятно , не считается блокатор.

Кроме того, @Kimundi , я рад видеть, что команда libs готова дать этому немного больше времени, чтобы решить эту

Согласен с @jonhoo, что Pinned все еще странно себя чувствует, а PhantomPinned не лучше (он никоим образом не закреплен, даже фантомным образом). Я думаю, что если мы найдем хорошее имя для Unpin , тогда Pinned , естественно, поддается переименованию в Not{NewNameForUnpin} .

Я действительно не думаю, что нам нужно больше тратить время на обсуждение PhantomPinned - этот тип почти никогда не появится для конечных пользователей. PhantomNotUnpin / PhantomNotDePin / PhantomNotMoveFromPin / и т. Д. Не сделают его более или менее распространенным и не сделает API более или менее очевидным для пользователей, которые уже достаточно освоили с API, чтобы придумать законное использование для PhantomPinned .

Просто небольшая идея: трейт Move и ZST Anchor .

Каждый тип может быть Move , если он не содержит Anchor который заставляет его придерживаться Pin .
Мне нравится, что Anchor: !Move интуитивно понятны.

Я не предлагаю тратить время конкретно на PhantomPinned , но я думаю, что было бы несложно сохранять непредвзятость, потому что вполне возможно, что все, что было получено за Unpin будет работать так же хорошо для PhantomPinned .

Мы рассмотрели Move и много раз объясняли, почему это неуместно. Все типы подвижны, пока не будут закреплены. Точно так же ранее предлагалось Anchor , но из названия совершенно не ясно, что он используется для отказа от Unpin ing / Move ing.

@stjepang Я думаю, что Move было отброшено некоторое время назад, потому что что-то, что является !Unpin , на самом деле не препятствует его перемещению. Только если тип меньше Pin , и это не Unpin , вы "по контракту обязаны" не перемещать его.

В исходном комментарии @withoutboats говорится:

Обертка Pin изменяет указатель, чтобы "закрепить" память, на которую он ссылается, на месте

Я думаю, что это странно, что типы реализуют Unpin потому что значения не «закреплены», а память. Однако имеет смысл говорить о ценностях, которые «безопасно перемещать». Что, если мы переименуем Unpin в MoveSafe ?

Рассмотрим черту UnwindSafe . Это просто "намекающий" маркер, и его можно безопасно реализовать. Если вы позволите значению !UnwindSafe пересекать границу catch_unwindAssertUnwindSafe ), вы "сломаете" его, аннулировав его инварианты.

Точно так же, если у вас есть значение !Unpin / !MoveSafe , его все равно можно переместить (конечно), но вы «сломаете» его, сделав недействительными его ссылки на себя. Концепция кажется похожей.

Черта Unpin самом деле просто означает MoveSafe . Мне кажется, дело не в значениях, которые можно вынести из памяти за Pin . Скорее, речь идет о ценностях, которые не «сломаются» при перемещении.

MoveSafe имеет ту же проблему, что и Move - все значения любого типа можно безопасно перемещать. Переместить значение становится невозможно только после того, как оно было закреплено.

MoveSafe имеет ту же проблему, что и Move - все значения любого типа можно безопасно перемещать.

Верно, но это другое значение слова «безопасный», как в UnwindSafe . В любом случае, меня бы устроило Relocate или что-нибудь в этом роде.

Подводя итог, я считаю, что имя признака не должно быть DePin , Unpin или что-либо, содержащее «булавку» в названии. Для меня это главный источник путаницы. Эта черта на самом деле не является «выходом из оков» Pin - она ​​говорит, что значение не будет аннулировано при перемещении.

Я просто вижу Pin и Unpin как совершенно разные вещи. :)

Я чувствую с точностью до наоборот;). Эта черта имеет смысл только в отношении Pin, который является единственным имеющимся у нас типом, который содержательно выражает ограничения на подвижность базового значения. Без PIN-кода открепление совершенно бессмысленно.

Мне нравится думать о закреплении, как о размещении чего-либо на доске. Если вы удалите булавку объекта, прикрепленного к доске, вы можете переместить объект. Удаление булавки открепляется.
Мне нравится имя Unpin .

Я также вижу, как! Открепление - это двойное отрицание и может вызвать путаницу. Однако мне интересно, как часто нужно писать !Unpin .

Еще одно имя, которое я мог бы придумать для Unpin, - Detach . Возвращаясь к метафоре доски, вы не будете _Unpin_, но _Detach_ Pin от его объекта.

Думаю, мне очень нравится DePin ! Пока что это мой любимый вариант - он краток, это явно глагол, а не прилагательное, и !DePin тоже кажется довольно ясным («не может быть удалено»).

Я думаю, что это странно, что типы реализуют Unpin, потому что значения не «закреплены», а память.

Значение закреплено в памяти. Но и здесь значение имеет решающее значение. Для меня закрепление памяти - это просто гарантия того, что она остается разыменованной или около того, но она не нарушается mem::swap . Закрепление значения в памяти заключается в том, чтобы не перемещать это закрепленное значение в другое место, что и есть в Pin .

Я также вижу, как! Открепление - это двойное отрицание и может вызвать путаницу. Однако мне интересно, как часто нужно писать! Открепить.

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

У меня нет кода под рукой, но в первый раз, когда я попытался использовать фьючерсы, я хотел, чтобы функция возвращала impl Future<Output=T> . Я не могу вспомнить, что произошло, но я сразу получил грубую ошибку компилятора с жалобами на T и Unpin. Мне нужен был ответ на вопрос: «Безопасно ли ограничивать T только откреплением». И это заставило меня смотреть в бездну около двух мучительных часов.

«О, хорошо, если это то, что означает Пин. Значит,« Открепить »означает… Коробка? Почему это черта?

«Подожди, impl !Unpin ? Почему это не impl Pin

«Верно, закрепление и открепление ... не противоположности. Это совершенно разные вещи. Постой, тогда что же снова означает открепление? Почему это так называется?»

«Что, черт возьми, означает !Unpin ? Не ... что-то другое, не булавка? Hrrrgh»

Тем не менее, единственный способ, которым это имеет смысл в моей голове, - заменить «открепить» на «перемещаемый». «Тип не может быть перемещен в памяти» имеет смысл. И все же, даже зная, что делает Пин , «тип не открепить» меня смущает.

Переименование Unpin в Relocatable (или Relocate ) получает мой голос 🗳. Но я бы нашел любое другое предложение лучше, чем «Открепить».

Эта черта имеет смысл только в отношении Pin, который является единственным имеющимся у нас типом, который содержательно выражает ограничения на подвижность базового значения. Без PIN-кода открепление совершенно бессмысленно.

Чтобы подчеркнуть это - гарантии, связанные с булавкой, полностью связаны с определенным поведением , а не с типами . Например, функция генератора, которая возвращает () может тривиально реализовать FnOnce , многократно возобновляя выполнение. Хотя этот тип может не реализовывать Unpin - поскольку его состояния доходности могут быть самореферентными, его интерфейс FnOnce (который перемещается сам) полностью безопасен, поскольку он еще не был закреплен, когда вы FnOnce it, что необходимо для приведения его в самореферентное состояние. Unpin относится конкретно к типам поведения, которые считаются безопасными после закрепления типа (а именно, его перемещению), а не к некоторому внутреннему свойству типа.

Парадоксально я только приехал сюда , чтобы комментарий, в то время как Unpin именования определенно был обсужден в прошлом, дебаты по этому я помню свидетельствование, когда мы решили заменить Move с Unpin , что, как я собирался сказать, явное улучшение. И часто это можно понять только позже, хотя текущий дизайн является определенным улучшением по сравнению с тем, что было раньше, у него все еще есть место для дальнейшего улучшения. В каком направлении я к этому пришел.

Открепление конкретно касается тех типов поведения, которые считаются безопасными после закрепления типа (а именно, его перемещения), а не какого-либо внутреннего свойства типа.

Поведение при закреплении является внутренним свойством типа, точно так же, как поведение / инвариант при совместном использовании. Я подробно остановился на этом в блоге .

@RalfJung, мы говорим мимо друг друга. Есть путаница. Я часто вижу, что самореферентные типы «не могут быть перемещены» - это неверно, у них есть определенные состояния, в которые они могут войти, во время которых их нельзя перемещать, но пока они находятся в других состояниях, это совершенно безопасно. для их перемещения (а наши API-интерфейсы полагаются на возможность перемещать их, например, применять к ним комбинаторы или перемещать их в Pin<Box<>> ). Я пытаюсь уточнить, что это не тот случай, когда эти типы «нельзя перемещать».

Ах да, тогда я согласен. Тип !DePin не всегда закреплен , а когда это не так, его можно перемещать, как и любой другой тип.

@cramertj @withoutboats кое-что, о чем я пока не догадываюсь: вы против переименования Unpin ? Не похоже, что вы все согласны с тем, что его нужно переименовать, но я не уверен, что вы против этого.

Я лично не думаю, что основная проблема здесь кроется в имени Unpin и в том, что «если бы мы могли просто переименовать его, все было бы интуитивно понятно». Хотя переименование может немного помочь (и Relocate / DePin здесь кажутся хорошими ...), я думаю, что основная сложность связана с самой концепцией закрепления; это определенно не одно из самых простых понятий для понимания или объяснения; даже не близко.

Поэтому я думаю, что документация по модулю core::pin должна быть _существенно__ усилена и необходимо включить гораздо больше примеров. В хороших примерах будут показаны как канонические варианты использования, так и использование небезопасных функций и несостоятельных реализаций.

@alexcrichton Я не против переименования, нет. Я думаю, что Unpin уже в порядке, но я бы согласился с DePin , поэтому я и предложил это. RemoveFromPin является наиболее очевидным "правильным", но многословным до синтаксической соли, поэтому я думаю, что я бы специально выступил против этого имени. Я, однако, против того, чтобы отложить стабилизацию на неопределенный срок до тех пор, пока мы не найдем имя, которое, по общему мнению, является лучшим - я думаю, что у API есть некоторые присущие ему сложности, которые не станут значительно лучше или хуже из-за названия Unpin черта. Я действительно хотел бы в ближайшее время продвинуться к стабилизации, чтобы предотвратить отток кода, документации и обсуждений вокруг futures_api (и поэтому мы можем приступить к работе по стабилизации futures_api сам). Возможно, нам следует запланировать специальную встречу венчурных капиталистов для выяснения имен, чтобы каждый, у кого есть мнение, мог представить свое решение, а у нас была возможность решить эту проблему с помощью более широкой полосы пропускания?

Мой голос: std::pin::Reloc (Переместить)

У меня есть две очень гипотетические ситуации (ну, на самом деле только одна, но два разных исполнения), из которых я не возражал бы, чтобы прямо заявили, разрешено это или нет.

Unpin указывает на то, что переход из всех состояний T (где T: Unpin ) тривиален, находясь в закрепленном состоянии типа (надеюсь, я правильно помню этот термин из одного из сообщений в блоге Pin может раздавать &mut T .

Итак, давайте предположим, что я хочу создать тип Woof для которого также всегда можно перейти из всех состояний Woof , находясь в закрепленном состоянии типа, в состояние без закрепления. состояние, но это нетривиально, и поэтому нельзя реализовать Unpin , можно ли для Woof иметь такую ​​функцию, как fn unpin(self: Pin<Box<Woof>>) -> Box<Woof> ?

То же самое для типа Meow для которого только иногда возможен переход от закрепленного к не закрепленному, а также для такой функции, как fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>> .

Мои 2 цента за велосипедную хеддинг:

Если я правильно понимаю, Unpin самом деле означает « Pin не оказывает на меня никакого воздействия».

А что насчет чего-то вроде BypassPin или IgnorePin ?

Итак, давайте предположим, что я хочу создать тип Woof, для которого также всегда можно перейти из всех состояний Woof в закрепленном состоянии типа в состояние типа без закрепления, но это нетривиально и может поэтому не реализовать Unpin, может ли Woof иметь такую ​​функцию, как fn unpin (self: Pin>) -> Коробка?

Да, это должно быть возможно. Например, если Woof является элементом навязчивого связанного списка, он может предоставить функцию для удаления элемента из списка и одновременного удаления Pin (утверждая, что для не -enqueued Woof s, два состояния типов эквивалентны, поэтому мы можем удалить их).

Relocate для меня имеет ту же проблему, что и RePin , это может означать операцию, позволяющую переместить значение из одного закрепленного местоположения в другое, не открепляя значение полностью. Это менее сильный оттенок, чем у RePin , но все же кажется немного запутанным.

это может означать операцию, позволяющую переместить значение из одного закрепленного места в другое, не открепляя значение полностью.

Хотя это не совсем то, о чем идет речь, это тоже не совсем неверно: для большинства случаев использования перемещение значения из одного закрепленного места в другое так же катастрофично, как и его свободное использование. На самом деле, учитывая, что любой может создать Pin<Box<T>> , я даже не понимаю, почему вы проводите здесь фундаментальное различие.

В большинстве случаев перемещение значения из одного закрепленного места в другое так же катастрофично, как и его свободное использование.

Да, именно поэтому наличие признака, добавляющего эту операцию к типу, может быть полезно (это не может быть признака маркера, такого как Unpin поскольку ему может потребоваться что-то вроде обновления внутренних ссылок). Я не предлагаю добавлять что-то подобное сейчас, просто это то, что, как я могу видеть, будет предоставлено в будущем (либо в std или у третьих лиц), что может привести к сбивающему с толку совпадению имен.

Сейчас я не знаю ни одного сильного случая его использования, но я подумал о том, чтобы использовать что-то подобное с закрепленными коллекциями.

Хорошо, вот некоторые размышления. Изначально мне было интересно, можем ли мы иметь такое имя, как PinInert или PinNoGuarantees как это тоже то, что есть в описании, но, думая о том, что мне действительно нужна черта, описывающая действие, собственности, или, по крайней мере, мне намного легче думать о ней в голове.

Одна проблема с Unpin (я не уверен, почему) заключается в том, что я не могу понять, что предполагаемое значение - это "действие по удалению булавки, откреплению, безопасно для выполнения". ". Черта как есть передает действие «открепление», но я, кажется, не совсем понимаю это, когда читаю ее. Странно иметь Pin<T: Unpin> потому что если он открепляется, почему он находится в пине?

Интересно, могло бы работать такое имя, как CanUnpin ? Во многом это не связано с жесткой гарантией, так или иначе (реализация Unpin не означает, что вы удалите его с булавки, это просто означает, что вы можете удалить его с булавки). Как это звучит? Достаточно ли CanUnpin читается для других? Достаточно короткий?

(наличие префикса Can передает глагол мне гораздо легче, и в нем говорится, что вы можете удалить его с булавки, но вы не всегда будете это делать).


В качестве еще одного несвязанного касательного момента, я забыл упомянуть ранее (извините за это!), Что я не думаю, что все вышеперечисленные методы обязательно должны быть внутренними методами. У нас уже было множество ошибок, связанных с методами вывода и конфликтов, и добавление очень распространенных имен, таких как as_ref и as_mut для типов, которые также реализуют Deref кажется проблемой. ждет, чтобы случиться.

Достаточно ли часто эти операции происходят, чтобы оправдать рискованное место их размещения? (по сути) Или они используются достаточно редко, чтобы можно было нарушить безопасный путь связанных функций?

Поскольку у меня, по-видимому, сейчас есть скин в этой игре: CanUnpin кажется мне очень близким по духу к Unpinnable или чему-то подобному, и у меня всегда складывалось впечатление, что сообщество неодобрительно относится к таким модификаторам в имена трейтов, поскольку большинство трейтов описывают действие, которое может предпринять тип. Само имплицит является подразумеваемым «может» или «-аблемент» в этом смысле. Что бы ни было решено (и имя не имеет значения в той степени, в которой здесь указано IM-not-so-HO - очень немногим пользователям, вероятно, придется это вводить), я бы посоветовал всем почувствовать некоторую срочность в решении вопросов здесь. Я очень рад этой стабилизации, и я знаю, что многие, многие люди, тоже!

@alexcrichton

Одна проблема с Unpin (я не уверен, почему) заключается в том, что я не могу понять, что предполагаемое значение - это "действие по удалению булавки, откреплению, безопасно для выполнения". ".

Что, если вы подумаете о T: Unpin как о « T невосприимчив к Pin »? Тогда , если у вас есть Pin<T: Unpin> это просто означает , что у нас есть значение внутри Pin , который невосприимчив к прижав так прижав здесь эффективно спорно.

Другими словами: Unpin нейтрализует Pin . Честно говоря, после стольких дискуссий я как бы усвоил это, и теперь это имеет смысл. 😆

Во многом это не связано с жесткой гарантией, так или иначе (реализация Unpin не означает, что вы удалите его с булавки, это просто означает, что вы _ можете_ удалить его с булавки).

Контрапунктом этому является черта Send . Это не означает , что вы будете посылать значения, это просто означает , что вы можете отправить его.

@anp Я согласен, что CanUnpin - не лучшее имя, я изо всех сил пытаюсь придумать имя получше! Кажется, немногие до сих пор считают, что это нужно переименовать, хотя, поскольку все предложения, похоже, отклоняются в основном по тем же причинам, по которым я чувствую, что Unpin нужно переименовать в первую очередь.

Также напоминаем, что любая стабилизация идет в поездах, как и все другие изменения, и с выпуском на следующей неделе это определенно не войдет в этот выпуск и будет кандидатом на следующий выпуск. Это означает, что у нас есть 7 недель, почти два месяца, чтобы получить все это как можно скорее. Хотя я согласен, что срочность необходима, это просто «давайте не будем забывать об этой срочности», а не «давайте решим это до понедельника».

@stjepang Я тоже привык к Unpin после того как так много об этом подумал! Все альтернативные имена на данный момент кажутся довольно тусклыми, поэтому я подхожу к тому моменту, когда я бы согласился на лучшую документацию. В идеале я хотел бы найти кого-то, кто мало что знает о Pin apis, чтобы потом прочитать указанную документацию, чтобы дважды проверить, что этого достаточно для обучения.

Я также хотел бы пойти дальше и официально заблокировать стабилизацию в улучшенной документации, поскольку это особенно важно для этой проблемы.

@rfcbot касается улучшения-документации

В частности, мне нужна лучшая документация:

  • Подробное изложение каждого метода о том, почему он безопасен и / или почему он небезопасен. Например, контракт реализации P DerefMut "разумного поведения" не задокументирован в Pin::new_unchecked .
  • Каждая функция unsafe должна иметь пример кода, объясняющий, почему она небезопасна, или, по крайней мере, четкое объяснение последовательности шагов, которые могут пойти не так.
  • Я чувствую, что документация модуля может содержать более подробную информацию, помимо того, что такое Pin и что оно означает. Я думаю, им будет полезна информация типа «почему это не общие типы недвижимого имущества, а его форма» или «как на практике используется Pin , например, с фьючерсами».
  • Я хотел бы увидеть пример с дженериками и Unpin либо в документации, либо в примерах кода. Например, похоже, что многие комбинаторы в Futures должны справиться с этим, и точно так же некоторые комбинаторы требуют этого. Некоторые примеры использования дженериков и того, как они работают, могут немного помочь понять Unpin .

Мне также любопытно, есть ли у других конкретные действия, которые можно добавить в документацию!

Затем я также хотел бы формально заблокировать self -вопрос с помощью as_ref , но я подозреваю, что это будет быстро разрешено. (снова извините за то, что не забыл поднять этот вопрос раньше)

@rfcbot касается самостоятельных методов

Это предлагает as_ref , as_mut , get_ref , get_mut , into_ref и set всем методам be на Pin type. В предложении упоминается, что мы не делаем этого для интеллектуальных указателей из-за конфликтов методов, но цитируется, что опыт показывает, что реализованный сегодня API Pin не является согласованным и часто заканчивается тем, что просто мешает.

Эти имена методов, однако, очень короткие и приятные, и их довольно часто можно найти во всей экосистеме. В прошлом команда libs сталкивалась с бесконечной серией ошибок, когда мы добавляли реализации трейтов и тому подобное, что приводило к конфликтам с существующими методами трейтов в различных ящиках. Эти методы кажутся особенно опасными из-за их названия. Кроме того, неясно, сможем ли мы когда-либо добавить больше методов в Pin в будущем, поскольку даже если имена в конечном итоге будут работать, любое добавленное имя в будущем имеет высокий риск коллизии.

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

И пока я занимаюсь этим, я заметил еще одну вещь, и снова я думаю, что это довольно быстро разрешится.

@rfcbot проблема box-pinnned-vs-box-pin

Считалось ли имя Box::pin за Box::pinned ? При наличии Pinned и / или PhantomPinned кажется, что было бы неплохо, если бы имя могло соответствовать возвращаемому типу!

@alexcrichton Уговаривает ли вас что-нибудь, в частности, против варианта MoveFromPin ? Я вижу, вы раньше относились к нему благосклонно (и многим это тоже понравилось). Для протокола, прямые возражения, которые я мог вспомнить или быстро найти с помощью Ctrl + F-ing, заключаются в том, что «имена, состоящие из целых фраз ... были бы очень неидиоматичными» (@withoutboats) и что это «слишком многословно» ( @cramertj).

Я должен признать, что мотивация в духе «я привык ... теперь, после того, как я так много думал об этом», заставляет меня довольно беспокоиться. Люди могут привыкнуть и рационализировать постфактум практически все. Независимо от того , что имя, оно не будет большим сюрпризом , если мы не в конечном итоге привыкает к нему. (Это та самая причина, по которой мы выбираем явно субоптимальные имена, такие как existential type качестве временных в других случаях, чтобы избежать их постоянного превращения.)

Случайное предпочтение точки зрения опытных пользователей (а мы все!) Перед другими, которые будут делать что-то с более свежим мозгом, - это ошибка, которую я думаю, мы должны быть всегда бдительны, чтобы ее не совершать, сколь бы серьезной она ни была .

Хотя это, конечно, не стиль std lib, я также чувствую, что CanUnpin каким-то образом проясняет, как эта конкретная часть сочетается с другими.

Невозможно придумать имя, которое описывает значение Unpin для новичка, не читая документации. Send и Sync тоже трудно понять новичку, но они выглядят красиво и лаконично, как и Unpin . Если вы не знаете, что означают эти имена, вы должны прочитать их документацию.

@valff Действительно, это правильно, однако есть немало людей, которые прочитали документацию и понимают, как работает закрепление, но имя Unpin прежнему вызывает у них значительные умственные трудности, тогда как другие имена вызывают меньше.

@glaebhoerl о, извините за пояснение, я лично больше фанат MoveFromPin или CanUnpin чем нынешний Unpin . Просто пытаюсь помочь сделать вывод!

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

  • Расширение гарантий Pin на стабильный адрес до тех пор, пока Drop предъявит дополнительные требования к создателю Pin . Было бы полезно, если бы эти необходимые предварительные условия для вызова unsafe fn new_unchecked(pointer: P) -> Pin<P> были упомянуты напрямую. Понимание того, как можно создать Pin очень помогает понять их концепцию, imo.
  • Сводка, упомянутая в проблеме отслеживания, с префиксом we agreed that we are ready to stabilize them with no major API changes. , содержит много старых API. В то же время он также содержит много информации о Drop и проекции булавки, которая имеет отношение к делу. Он также содержит четкую формулировку дополнительной гарантии, упомянутой в первом пункте, которой нет в этом отчете. Это сочетание устаревшей и свежей информации немного сбивало с толку, подумайте о том, чтобы перенести всю информацию в этот выпуск или указать устаревшие абзацы.
  • Поскольку дополнительная гарантия называется slight extension , я ожидал, что будет какой-то способ отказаться от нее. Поскольку закрепление один раз переносит состояние типа для этого указателя до тех пор, пока он не будет отброшен, некоторый способ вернуться из состояния этого типа в «нормальное» состояние. Фактически, это было то, что я думал сначала сделать Unpin , но я думаю, что Unpin на самом деле немного сильнее. В нем говорится, что закрепленное состояние может быть свободно преобразовано в незакрепленное состояние по желанию. Это нормально, что текущий интерфейс минимален в этом отношении, но Unpin можно спутать с операцией над типом, которая удаляет некоторый внутренний инвариант, требующий состояния вывода (например, мягкая версия Drop ) .

Уточнение: я лично предпочитаю MoveFromPin другим именам, но это, возможно, искусственно и неуклюже.

Основной практический конкретный элемент, который приходит на ум, помимо улучшений документации, уже запрошенных @alexcrichton , будет объяснять причины правила поля для проекции штифта в map_unchecked и map_unchecked_mut . Что-то вроде:

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

Как насчет имени ElidePin для автоматически производной характеристики маркера?

Было бы зафиксировано, что тип всегда можно рассматривать как если бы он был открепленным. И !ElidePin тоже кажется вполне ясным, хотя это может быть субъективным.

Стандартный маркер также не имеет идеального названия для понимания закрепления. Pinned кажется, вызывает представление о том, что сама содержащая структура закреплена, но закрепление применяется к указателям и только после явного действия. Это не так критично, но и немаловажно, тем более что это может быть тип, который впервые встречается в реализации для самореферентного типа.

Мое общее возражение против MoveFromUnpin / RemoveFromUnpin состоит только в том, что они слишком многословны и уменьшат эргономичность ручных реализаций Future . CanUnpin / DePin Мне оба кажутся хорошими - хотелось бы, чтобы было более ясно, что Unpin следует шаблону Read / Write /и т.д. но кажется, что это не интуитивно понятно для людей, поэтому я добавлю +1 ко всему, что проясняет это, не выглядя синтаксической солью.

Я согласен, что NotWhateverUnpinBecomes , вероятно, лучшее имя для Pinned . Тем не менее, Adhere означает как прислушиваться, так и придерживаться. : Little_smiling_face:

CanUnpin / DePin кажутся мне прекрасными - я хотел бы, чтобы было более ясно, что Unpin следует шаблону чтения / записи / и т. Д.

Я думаю, что одна из вещей, которая делает Unpin сложным, в отличие от Read - это то, что это маркерная черта. Read легко понять, потому что существует метод Read::read - все, что вам нужно знать, прямо здесь, в trait . Если x равно Read я понимаю, что могу назвать x.read() - аналогично write для Write и т. Д. Труднее объяснить, что X реализация Unpin означает, что Pin<Ptr<X>> реализует DerefMut - что означает, что вы можете обращаться с этим, как если бы это было просто X .

Читать легко, потому что есть метод Read :: read

Если бы только мы могли добавить всегда применимые методы в определения auto trait + GAT, мы могли бы иметь Unpin::unpin - fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self> ). ..... подумав, я не думаю, что это кого-то менее запутает;)

(если серьезно, я бы поддержал Pin::unpin за переход от Pin<P<T>> к P<T> )

Я бы поддержал Pin :: unpin для перехода с Pin

> в P

Это путает в моей голове два термина, как и само название. unpin звучит очень похоже на отмену гарантий состояния типа для сравнения, которое выглядело бы так, как если бы существовал метод fn unborrow(&'_ T) или fn unmove(??) . Поскольку pin предоставляет гарантии до тех пор, пока некоторая память, представляющая тип, не станет Drop::drop ed, мы на самом деле не меняем состояние, тип просто гарантирует, что все другие представления поддерживают эквивалентные гарантии, и поэтому мы можем игнорировать это . Это также основная разница, которую я вижу между чертами маркера и чем-то вроде io::Read . Один разрешает операции для компилятора или языка, а другой разрешает операции для программиста.

Более того, это важный момент, что в настоящее время это невозможно точно представить, просто используя правильный набор текста. Вызов операции unpin звучит так, как будто существует обратная функция pin . Эта функция также немного ошибочно намекает, что такая операция открепления каким-то образом связана с вычислительной работой, например, into_pointer() будет лучше в этом отношении, если также будет следовать более устоявшемуся соглашению об именах.

Наконец, я думаю, что есть потенциал для типов, специально имеющих функцию unpin с вычислительной работой. Тип с очень особыми внутренними инвариантами мог бы «исправить» свое внутреннее состояние таким образом, чтобы он мог предложить интерфейс fn unpin(self: Pin<&'a mut T>) -> &'a mut , где он добровольно теряет все гарантии Pin течение некоторого времени жизни. 'a . В этом случае оба вышеуказанных пункта больше не применяются. Такую функцию можно представить, как если бы она имела эквивалентный эффект отбрасыванию и восстановлению в том же месте памяти (таким образом, фактически удаляя состояние типа). И это может потребовать вычислений, например, путем перемещения некоторых ссылок на себя в динамическое размещение.

Было бы прискорбно, если бы сбивающее с толку имя также затруднило бы разработчикам и разработчикам библиотек выбор не сбивающих с толку имен, объединяя эти две идеи.

Мое общее возражение против MoveFromUnpin / RemoveFromUnpin состоит в том, что они слишком многословны и могут ухудшить эргономику ручных реализаций Future.

Я не думаю, что это правда, особенно для MoveFromPin , которое мне кажется разумным для ввода - и при любом умном или глупом автозаполнении проблема в любом случае практически отсутствует.

Важной частью эргономики также должна быть читабельность и понятность кода. Rust уже вызывал некоторую критику в прошлом за агрессивное сокращение вещей ( fn , mut и т. Д.), Что затрудняет чтение кода. Для вещей, которые для концепции, которая еще более сложна для понимания и которая выполняет нишевую цель для большинства пользователей, использование более подробного и описательного имени должно быть полностью приемлемым.

@rfcbot разрешает именование

Хорошо, я уже какое-то время готовлюсь, а также @withoutboats . Я пришел к выводу, что теперь мне лично комфортно, по крайней мере, с именем Unpin в качестве маркерной черты. В результате я собираюсь снять возражение против блокировки (хотя, если другие в команде libs считают иначе, сообщите нам об этом!)

Основная причина, по которой я пришел к Unpin в основном состоит в том, что @cramertj уже сказал выше , это самое идиоматическое название для этой черты, которое мы смогли придумать. Все другие предложенные мной имена черт не соответствуют идиоматической планке, которой соответствует Unpin (или, по крайней мере, на мой взгляд).

Хотя я все еще верю, что есть более подходящие названия для выражения «Я только что увидел название этой черты и хочу знать, что она делает», я не думаю, что здесь стоит решать эту проблему. На данный момент мне ясно, что мы не можем, по крайней мере в настоящее время, придумать другое имя для этой черты, которое также является идиоматическим, но мы скорее переключаемся на имена черт, которые решают другую проблему. Понимание Unpin аналогично Send и Sync , необходимо прочитать документацию, чтобы полностью понять, что происходит.


В качестве еще одного уточняющего момента я перечислил несколько «блокирующих возражений», но они больше относятся к TODO, чем к блокирующим возражениям как таковым. У меня просто нет отличного способа перемещаться по такой длинной цепочке! В этом ключе я думаю, что это нормально, если PR стабилизации будет опубликован в любое время, когда FCP истек в течение недели или около того. Здесь можно вкратце обсудить последние моменты, касающиеся self / pin / pinned (при необходимости их можно оставить как предложение выше).

Документация, как мне кажется, не является обязательным условием для стабилизации в данном случае. У нас есть полный цикл (6 недель) для добавления документов до того, как эти типы станут стабильными, и у нас есть еще больше времени, чтобы подкрепить документацию здесь, прежде чем полная история async / await станет стабильной. Пора улучшить то, что есть сейчас, а то, что есть сейчас, уже весьма полезно!

Что здесь вообще означает «идиоматика»? Что унидиоматично в Reloc[ate] , Escape , Evade , Pluck или Roam ? Все это глаголы, и ни один из них не может быть ошибочно принят за двояко-отрицательную проблему.

@alexcrichton Есть ли причина, по которой Unpin считается более идиоматическим, чем Depin или DePin ?

Я думаю, что Depin - очень надежная альтернатива, и она не вызывает у меня таких же умственных трудностей, как Unpin .

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

@jimmycuadra В Rust много имен, которые не являются «настоящими» словами, в том числе в stdlib.

Я был бы удивлен, если бы это сочли важной причиной выбрать одно имя вместо другого.

@Pauan Это серьезная причина. Как носитель английского языка, для меня Depin звучит так, как будто мы забыли, что слово для открепления существует, и пытались его придумать. Мне это кажется вопиюще неправильным с грамматической точки зрения: английское слово «depinning» - «открепление».

Хорошая аналогия была бы, если бы у нас были API, в которых говорилось бы «разблокировать» вместо «разблокировать».

@jaredr под "идиоматическим" я подразумеваю следование установленным соглашениям стандартной библиотеки, уже подобным (ранее упомянутым) признакам Read и Write . У нас есть соглашение о не многословных именах, глаголах, где это возможно, коротких и соответствующих ситуации.

Все имена, подобные предложенным вами, возможны, но Unpin (по крайней мере, мне кажется) является наиболее подходящим для этого конкретного действия (гарантия "вы можете открепить этот тип"). Остальные, хотя и нечеткие синонимы Unpin , я думаю, в основном переименовывают только слово "открепить" и иногда не передают ту же связь с Pin как Unpin делает.

@Pauan Я согласен с @withoutboats в том, что Depin не похоже на дживы, а также Unpin .

@aturon отметил в https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636, что:

Использование Pin - это не больше деталей реализации, чем выбор &self против &mut self ; в долгосрочной перспективе вполне вероятно, что Pin сам по себе будет отдельным способом справки с поддержкой языка. Во всех этих случаях в подписи передаются разрешения, предоставляемые вызываемому, при этом Pin запрещает выход за пределы ссылки.

Якобы это относится к родному типу &pin или чему-то еще ... но я не понимаю, как это сочетается с Pin<&mut T> и т. Д. В моих разговорах с @withoutboats и, в частности, с @cramertj они совершенно не были уверены в идее отдельного режима справки с языковой поддержкой и в том, как мы могли бы добраться туда из Pin<T> .

Перед стабилизацией pin было бы разумно согласовать эти взгляды, чтобы гарантировать, что мы находимся на одной странице относительно. эта критически важная часть инфраструктуры; так что я бы в основном хотел, чтобы Аарон расширил это, а затем Боты и Тейлор взвесили.

Другие, хотя и нечеткие синонимы Unpin, я думаю, в основном переименовывают только слово «открепить» и иногда не передают той же связи с Pin, как Unpin.

@alexcrichton , я думаю, это действительно хорошо? Как Send для операции перемещения / копирования (=), Sync для операции заимствования (&). Может, такая связь на самом деле вызывает еще больше недоразумений?

@ crlf0710 может! Хотя я и не уверен, что согласился бы с такой связью. Send и Sync больше касаются того, что они позволяют ("отправлять" типы IN в другие потоки и "синхронизировать" доступ между потоками), и при их именовании мы не пытались не называйте их близко к той или иной операции

@alexcrichton именно так! так что, возможно, эта черта также должна быть связана с тем, что она позволяет. ("перемещение (Здесь глагол) из Pin ). Я не являюсь носителем английского языка, но я все еще думаю, что "открепление" от pin немного ... странно?

@ crlf0710 Но то, что позволяет <moving> вне пина, это <moving out of a Pin> . Проблема с move и синонимами для move в том, что они подразумевают, что черта управляет способностью типа двигаться вообще, чего он не делает. Связь с Пином жизненно важна для понимания того, что на самом деле делает черта.

Таким образом, наиболее идиоматическим названием для этой черты будет глагол, означающий «выйти из булавки», для которого слово «открепить» для меня наиболее очевидно. Вот определение «открепления» от Викисловаря:

  1. Отстегнуть, сняв шпильку.
  2. (переходный, вычислительный, графический пользовательский интерфейс) Чтобы отсоединить (значок, приложение и т. д.) от того места, где оно было ранее закреплено.

@withoutboats, спасибо за объяснение! На самом деле я думаю, что могу принять это имя Unpin или любое другое имя, которое наконец решит команда ржавчины, я не хочу вообще блокировать стабилизацию типов контактов, и я абсолютно понимаю, что опасения по поводу "перемещения" и т. Д.

Просто кажется, что где-то есть малейшее «повторение». и я должен убедить себя: это не означает «непобедимый», а наоборот. Думаю, через некоторое время я к этому привыкну, если это окончательное решение.

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

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

pin_proj! { let MyStruct { field1, field2 } = a_pinned_mutable_reference; }

чтобы разложить Pin<&mut MyStruct> на закрепленные изменяемые ссылки на поля.

Чтобы сделать это безопасным и удобным, нам понадобятся еще две вещи:

  • Тип Kite для обозначения полей "всегда- Unpin "
  • Сделать Unpin небезопасным

Последнее нужно для сохранности проекции. Без этого мы можем безопасно определить « Kite с закрепленными проекциями», что на самом деле неверно.

Имея это в виду, я предлагаю сделать Unpin небезопасным перед стабилизацией, чтобы оставить место для других полезных операций в безопасности.

@qnighy Тип может нарушить гарантии закрепления в своей реализации Drop , что делает Drop эквивалентно мощным Unpin . Если сделать Unpin небезопасным, дополнительный код не станет безопасным, потому что Drop также безопасен, и его нельзя изменить. Мы много говорили об этом по проблеме отслеживания, и об этом также упоминалось в этой ветке.

По сути, проекции булавок не могут быть безопасными без возможности утверждать определенные отрицательные границы, которые сегодня не могут быть выражены в Rust.

@ crlf0710

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

Detach кажется намного лучше, чем такие имена, как Move и Relocate, но, похоже, противоречит другим возможным применениям метафоры отсоединения (аналогично тому, как Escape может конфликтовать с «анализом выхода» и т. Д.). У нас очень мало метафор о том, как данные могут соотноситься с другими данными в информатике, что заставляет меня думать об еще одном новом преимуществе Unpin: плотно привязавшись к метафоре «булавки», он не занимает места для метафорического языка будущего нам может потребоваться использовать для другой цели, например, такие имена, как Detach, Escape или Relocate.

@withoutboats Я не предпочитаю какое-либо имя или большие инвестиции в этот велосипедный навес ... но мне любопытно; для какой другой цели подойдет Detach (если вы думаете ...)?

@Centril У меня нет четкого примера использования, но я могу представить себе какой-нибудь вариант использования, связанный, например, с деструктуризацией.

@withoutboats Да, в этом есть смысл; ура!

@withoutboats Я не уверен, является ли запись в Викисловаре лучшей мотивацией для оправдания присвоения имени Unpin для включения move from a pin . Метафоры физического представления страдают от того факта, что перемещение в Rust не приводит к удалению объекта в мире. Чтобы быть более точным, «подъем» закрепленного указателя не дает мне контроля над упомянутой памятью, его распределение и достоверность как объектное представление T по-прежнему требуются и гарантируются семантикой указателя. Следовательно, открепление не дает полностью контролируемого представления T , как при распаковке. И словарная статья, определяющая unpinning в терминах moving , на самом деле является скорее частью путаницы, чем оправданием такого имени.

В предлагаемом API ни один из методов не имеет такого эффекта (и это также не входит в сферу действия пина). Я не вижу безопасного способа, например, для перехода с Pin<Box<T>> на Box<T> (и, соответственно, на T ), и я не уверен, должен ли Unpin есть такие сильные возможности. Я не совсем уверен, в чем будет разница. Но насколько я понял, Pin применяется к некоторой ячейке памяти, тогда как Unpin гарантирует, что ничто в представлении T s не зависит от гарантий вывода. Однако будет ли это то же самое, что полностью вынуть и забыть память? Думаю, нет. Я придумаю более конкретный пример.

Изменить: более конкретно, даже если T равно Unpin , могу ли я полагаться на какой-то экземпляр T::drop(&mut) , вызываемый в закрепленной памяти, до того, как эта память будет освобождена? Насколько я могу судить по формализму, ответ должен быть положительным, но имя Unpin сообщает мне обратное.

Изменить 2: Rc позволяет наблюдать Pin<&T> но для T: Unpin прежнему не вызывается drop в исходной ячейке памяти. Сохраните ссылку за пределами булавки, тогда после того, как булавка будет сброшена, вы можете выйти с помощью Rc::try_unwrap . https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca Это эффективно отвечает на вопрос с помощью существующих механизмов, но работает ли он так, как задумано?

Возможно, IgnorePin ? Если T равно IgnorePin вы можете рассматривать Pin<Box<T>> как &mut T существу игнорируя присутствие Pin (AIUI также игнорирует Box ). Игнорировать - это глагол, я полагаю, что IgnorePin не является, но я не уверен, что это такое. IgnorePin описывает то, что он позволяет, но не описывает ограничения, наложенные на тип, но и Unpin .

@wmanley У меня была очень похожая идея, ElidePin , в каком-то комментарии выше, хотя тогда я не мог конкретно выразить, почему эта идея была более точной. Но я согласен с тем, что «один глагол» - это руководство по стилю для маркеров Rust. Хотя он также допускает более естественное отрицание в виде !ElidePin / !IgnorePin , это не оптимально.

@withoutboats Последующий вопрос: поскольку пин, кажется, определяется с точки зрения базовой памяти, как пин взаимодействует с ZST? Поскольку Pinned является ZST, даже ZST может быть либо Unpin либо нет. Я бы интуитивно сказал, что его представление в памяти никогда формально не аннулируется, поэтому T::drop(&mut self) никогда не нужно вызывать, очень похоже на то, как можно построить контакт из памяти &mut 'static _ например, из утечки Box . В то же время все это может быть неверным, и я вижу, как возможна другая интерпретация. Мне кажется, что эти настройки заслуживают внимания в документации.

Безопасно ли создать Pin<P<T>> с T: !Unpin а затем немедленно открепить значение, если гарантировано, что закрепление ничем не наблюдается? Это только неопределенное поведение, если закрепленное значение перемещается после того, как для него был вызван метод, подобный опросу?

@HeroicKatora К сожалению, сегодня возможно реализовать Drop для типа, который может быть !Unpin из-за дженериков, например:

struct S<T>(T); // `!Unpin` if `T: !Unpin`
impl<T> Drop for S<T> { ... }

@cramertj Спасибо, я тоже это понял.

@cramertj Значит, мы по умолчанию используем T: ?Unpin для общих границ? Есть ли еще одна причина, по которой не используется значение по умолчанию T: Unpin для существующих типов, таких как Sized ? Было бы несколько случаев, когда это было бы раздражающе строгим, но может ли эта дополнительная автоматическая привязка вызвать регрессию?

@HeroicKatora Это потребует ?Unpin границ для каждого типа в стандартной библиотеке и в кучу кода, который вообще не заботится о Unpin .

Также безопасно добавить поле PhantomPinned в качестве еще одного способа создания типа Drop +! Unpin.

Есть ли еще одна причина, по которой не используется T: открепить для существующих типов?

Открепить - это просто автоматическая функция, такая как «Отправить и синхронизировать», это предложение не включает новых языковых функций. Таким образом, он имеет ту же семантику, что и автоматические свойства, которые уже определены, а именно то, что они не применяются к универсальным шаблонам по умолчанию (в отличие от Sized, который является встроенным признаком компилятора, а не автоматическим признаком).

Это только неопределенное поведение, если закрепленное значение перемещается после того, как для него был вызван метод, подобный опросу?

Здесь нам должно быть ясно, что неопределенное поведение в этом контексте на самом деле не является традиционным типом UB. Скорее, этот код, наблюдая за типом в Pin, может делать предположения о семантике владения этим значением (а именно, что если он не реализует Unpin, он не будет снова перемещен или признан недействительным до тех пор, пока не будет запущен его деструктор). Это не UB в том смысле, что компилятор предполагает, что этого никогда не произойдет (компилятор не знает, что такое Pin).

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

@cramertj Я понимаю, это было бы немного прискорбно. Мне немного неудобно взаимодействие Pin с Drop в спецификации, но не на языке. Довольно противоречивый вопрос между правильностью интерфейса, удобством использования и выпуском в ближайшем будущем. Могу ли я получить разъяснения по поводу редактирования 2 выше:

Rc позволяет наблюдать Pin <& T>, но для T: Unpin по-прежнему не вызывает drop в исходной ячейке памяти. Сохраните ссылку за пределами вывода, затем после того, как вывод был сброшен, вы можете выйти с помощью Rc :: try_unwrap. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca

@HeroicKatora проблема с вашим кодом заключается в том, что Mem<'a>: Unpin , но используемая вами строка небезопасного кода основана на предположениях, которые применяются только к типам, которые не реализуют Unpin . Я отредактировал вашу суть, чтобы включить доказательство того, что Mem<'a>: Unpin : https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=201d1ae382f590be8c5cac13af279aff

Вы полагаетесь на привязку Unpin, когда вызываете Pin::new , который может быть вызван только для указателей, цель которых реализует Unpin.

Обнаруженная вами лазейка была учтена, поэтому перейти от Rc<T: ?Unpin> к Pin<Rc<T>> невозможно. Вы должны построить Rc в контакте с помощью Rc::pinned .

@withoutboats Просто хотел подтвердить, что T: Unpin действительно также отказывается от вызова drop перед признанием его недействительным. Возникает вопрос, не должно ли быть

fn into_inner(Pin<P>) -> P where P: Deref, P::Target: Unpin;

поскольку он не защищает никакую часть интерфейса, и разворачивание Pin<Box<T>> в Box<T> потенциально полезно (для T: Unpin конечно).

@HeroicKatora, вы правы, что эта функция будет безопасной. Я не возражаю добавить это, но я бы не хотел расширять API, который мы стабилизируем в данный момент , так как эта ветка уже содержит сотни комментариев. Мы всегда можем добавить его позже, когда возникнет необходимость, так же, как мы это делаем со всеми стандартными API.

Я бы просто сказал, что как именование, так и функциональность Unpin имеют гораздо больший смысл с этим обратным методом. И зная, что гарантии Pin действительно ограничены T: !Unpin . Это также было бы напрямую продемонстрировано существованием такого метода, вместо создания аварийных люков, чтобы показать возможность: smile:. И его документация была бы идеальным местом для объяснения (или еще раз ссылки) на ограничения. (Можно даже подумать о том, чтобы назвать его unpin вместо into_inner ).

Изменить: в основном это просто обобщение того, что уже есть.

impl<P> Pin<P> where P: Deref, P::Target: Unpin

  • fn unpin(self) -> P

имеет экземпляр для P=&'a mut T , что эквивалентно предложенному

impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin

  • fn get_mut(self) -> &'a mut T

Теперь, когда стабилизация прошла, точка PFCP кажется спорным, так что поэтому:

@rfcbot отменить

Есть ли какое-либо отслеживание, чтобы убедиться, что комментарии, разработанные, начиная с https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982, превращаются в документы? Похоже, мы уже слишком поздно для выпуска, поскольку бета-версия была разветвлена ​​... хотя это было явно указано здесь, чтобы произойти до стабилизации: /

Есть ли причина, по которой он все еще открыт? @Centril, ты закрыл и

@RalfJung Я пытался отменить FCP, но меня не слушали; @alexcrichton, можете ли вы отменить FCP?

Это стабильно, так что закрываемся.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги