Rust: [Stabilisierung] Pin-APIs

Erstellt am 7. Nov. 2018  ·  213Kommentare  ·  Quelle: rust-lang/rust

@rfcbot fcp zusammenführen
Funktionsname: pin
Stabilisierungsziel: 1.32.0
Tracking-Problem: # 49150
Verwandte RFCs: rust-lang / rfcs # 2349

Dies ist ein Vorschlag zur Stabilisierung der Bibliotheksfunktion pin , wodurch das "Fixieren" erfolgt.
APIs zum Bearbeiten von fixiertem Speicher, die auf Stable verwendet werden können.

(Ich habe versucht, diesen Vorschlag als umfassenden "Stabilisierungsbericht" zu verfassen.)

Stabilisierte Funktion oder APIs

[std|core]::pin::Pin

Dies stabilisiert den Typ Pin im Submodul pin von std / core . Pin ist
Ein grundlegender, transparenter Wrapper um einen generischen Typ P , der beabsichtigt ist
um ein Zeigertyp zu sein (zum Beispiel sind Pin<&mut T> und Pin<Box<T>> beide
gültige, beabsichtigte Konstrukte). Der Pin Wrapper ändert den Zeiger auf "pin"
Der Speicher, auf den es sich bezieht, verhindert, dass der Benutzer Objekte herausbewegt
dieser Erinnerung.

Die übliche Art, den Typ Pin verwenden, besteht darin, eine angeheftete Variante von einigen zu erstellen
Art des Besitzzeigers ( Box , Rc usw.). Die Standardbibliothek, die alle Zeiger besitzt
Geben Sie einen pinned -Konstruktor an, der dies zurückgibt. Dann, um die zu manipulieren
Alle diese Zeiger bieten eine Möglichkeit, sich in Richtung Pin<&T> zu verschlechtern
und Pin<&mut T> . Festgesteckte Zeiger können deref, wodurch Sie &T , können dies aber nicht
sicher veränderlich deref: dies ist nur mit dem unsicheren get_mut
Funktion.

Infolgedessen muss jeder, der Daten über einen Pin mutiert, die Daten aufrechterhalten
unveränderlich, dass sie sich nie aus diesen Daten herausbewegen. Dies ermöglicht anderen Code zu
Gehen Sie sicher davon aus, dass die Daten niemals verschoben werden, sodass sie enthalten können (z
Beispiel) Selbstreferenzen.

Der Typ Pin die folgenden stabilisierten APIs:

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

Merkmal impliziert

Die meisten der auf Pin implizierten Merkmale sind ziemlich rot, diese beiden sind wichtig für
seine Funktionsweise:

  • 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 ist ein sicheres Auto-Merkmal, das sich von den Garantien des Fixierens abhebt. Wenn die
Ziel eines angehefteten Zeigers implementiert Unpin , es ist sicher zu ändern
Dereferenzierung darauf. Unpin Typen haben keine Garantie, dass dies nicht der Fall ist
aus einem Pin .

Dies macht es so ergonomisch, mit einem fixierten Verweis auf etwas umzugehen, das
enthält keine Selbstreferenzen, da es sich um einen nicht angehefteten handelt
Referenz. Die Garantien von Pin nur für Sonderfälle wie
Selbstreferenzielle Strukturen: Diese Typen implementieren nicht Unpin , also haben sie
die Einschränkungen des Typs Pin .

Bemerkenswerte Implementierungen von Unpin in 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>

Diese kodifizieren die Vorstellung, dass Fixierung nicht über Zeiger hinweg transitiv ist. Das
ist, ein Pin<&T> steckt nur den tatsächlichen Speicherblock fest, der durch T in a dargestellt wird
Ort. Benutzer waren gelegentlich verwirrt und erwarteten, dass ein Typ
wie Pin<&mut Box<T>> steckt die Daten von T an Ort und Stelle, aber es steckt nur die
Speicher, auf den sich die angeheftete Referenz tatsächlich bezieht: In diesem Fall die Box
Darstellung, die ein Zeiger in den Haufen.

std::marker::Pinned

Der Typ Pinned ist eine ZST, die Unpin nicht implementiert. es erlaubt dir
Unterdrücken Sie die automatische Implementierung von Unpin auf Stable, wobei !Unpin impliziert
wäre noch nicht stabil.

Intelligente Zeigerkonstruktoren

Den Standard-Smart-Zeigern werden Konstruktoren hinzugefügt, um angeheftete Referenzen zu erstellen:

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

Hinweise zu Pinning & Sicherheit

In den letzten 9 Monaten haben die Pinning-APIs mehrere Iterationen durchlaufen
Wir haben ihre Ausdruckskraft und auch ihre Solidität untersucht
Garantien. Ich würde jetzt zuversichtlich sagen, dass sich die Pinning-APIs hier stabilisiert haben
sind gesund und nahe genug an den lokalen Maxima in Ergonomie und
Ausdruckskraft; das heißt, bereit zur Stabilisierung.

Eines der schwierigeren Probleme beim Fixieren besteht darin, festzustellen, wann die Ausführung sicher ist
eine Stiftprojektion : das heißt, von einem Pin<P<Target = Foo>> zu einem zu wechseln
Pin<P<Target = Bar>> , wobei Bar ein Feld von Foo . Zum Glück haben wir
war in der Lage, eine Reihe von Regeln zu kodifizieren, anhand derer Benutzer feststellen können, ob eine solche
Projektion ist sicher:

  1. Es ist nur sicher, ein Projekt zu pinnen, wenn (Foo: Unpin) implies (Bar: Unpin) : das
    ist, wenn es nie der Fall ist, dass Foo (der enthaltende Typ) Unpin während ist
    Bar (der projizierte Typ) ist nicht Unpin .
  2. Es ist nur sicher, wenn Bar während der Zerstörung von Foo niemals bewegt wird.
    Dies bedeutet, dass entweder Foo keinen Destruktor hat oder der Destruktor vorsichtig ist
    überprüft, um sicherzustellen, dass es sich niemals aus dem Feld herausbewegt, auf das projiziert wird.
  3. Es ist nur sicher, wenn Foo (der enthaltende Typ) nicht repr(packed) ,
    weil dies dazu führt, dass Felder verschoben werden, um sie neu auszurichten.

Darüber hinaus bieten die Standard-APIs keine sichere Möglichkeit, Objekte an den Stapel anzuheften.
Dies liegt daran, dass es keine Möglichkeit gibt, dies mithilfe einer Funktions-API sicher zu implementieren.
Benutzer können jedoch Dinge unsicher an den Stapel heften, indem sie dies garantieren
Verschieben Sie das Objekt nach dem Erstellen der angehefteten Referenz nie wieder.

Die pin-utils Kiste auf crates.io enthält Makros, die bei beiden Stapeln helfen
Fixieren und Stiftprojektion. Das Stack-Pinning-Makro fixiert Objekte sicher an das
Stapeln mit einem Trick mit Schatten, während ein Makro für die Projektion vorhanden ist
Das ist unsicher, aber Sie müssen die Projektionskesselplatte nicht einschreiben
was Sie möglicherweise anderen falschen unsicheren Code einführen könnten.

Implementierungsänderungen vor der Stabilisierung

  • [] Exportiere Unpin aus dem Vorspiel, entferne pin::Unpin Reexport

In der Regel exportieren wir Dinge nicht von mehreren Stellen in std erneut, es sei denn
eines ist ein Supermodul der realen Definition (zB Verkürzung)
std::collections::hash_map::HashMap bis std::collections::HashMap ). Dafür
Grund, der Reexport von std::marker::Unpin von std::pin::Unpin ist aus
Ort.

Gleichzeitig sind andere wichtige Markermerkmale wie Senden und Synchronisieren enthalten
im Auftakt. Anstatt also Unpin aus dem pin -Modul erneut zu exportieren, von
Wenn wir das Vorspiel einfügen, ist es unnötig, std::marker::Unpin zu importieren.
der gleiche Grund, warum es in pin .

  • [] Ändern Sie die zugehörigen Funktionen in Methoden

Derzeit verwenden viele der zugehörigen Funktionen von Pin keine Methodensyntax.
Theoretisch soll damit ein Konflikt mit derefablen inneren Methoden vermieden werden. Jedoch,
Diese Regel wurde nicht konsequent angewendet und hat nach unserer Erfahrung meistens
machte die Dinge nur unbequemer. Fixierte Zeiger implementieren nur unveränderliche
Deref, nicht veränderliches Deref oder Deref nach Wert, was die Fähigkeit zum Deref einschränkt
wie auch immer. Darüber hinaus sind viele dieser Namen ziemlich eindeutig (z. B. map_unchecked ).
und unwahrscheinlich zu Konflikten.

Stattdessen ziehen wir es vor, den Pin -Methoden den gebührenden Vorrang einzuräumen. Benutzer, die
Der Zugriff auf eine Innenmethode kann immer mit UFCS erfolgen, so wie sie wäre
erforderlich, um auf die Pin-Methoden zuzugreifen, wenn wir keine Methodensyntax verwendet haben.

  • [] Benennen Sie get_mut_unchecked in get_unchecked_mut

Die aktuelle Reihenfolge stimmt nicht mit anderen Verwendungen in der Standardbibliothek überein.

  • [] Entfernen Sie impl<P> Unpin for Pin<P>

Dieses Impl ist nicht durch unsere Standardbegründung für Unpin-Impls gerechtfertigt: Es gibt keine Zeigerrichtung zwischen Pin<P> und P . Seine Nützlichkeit wird durch die Impls für Zeiger selbst abgedeckt.

Dieses Futures-Impl muss geändert werden, um eine P: Unpin -Bindung hinzuzufügen.

  • [] Markiere Pin als repr(transparent)

Der Pin sollte eine transparente Hülle um den Zeiger sein, mit der gleichen Darstellung.

Verbundene Funktionen und größere Meilensteine

Die Pin-APIs sind wichtig, um Speicherbereiche sicher zu manipulieren
garantiert nicht ausgezogen werden. Wenn die Objekte in diesem Speicher dies nicht tun
Implementieren Sie Unpin , ihre Adresse wird sich nie ändern. Dies ist notwendig für
Erstellen von selbstreferenziellen Generatoren und asynchronen Funktionen. Als Ergebnis,
Der Typ Pin in den APIs der Standardbibliothek future angezeigt und wird in Kürze angezeigt
werden auch in den APIs für Generatoren angezeigt (# 55704).

Die Stabilisierung des Pin -Typs und seiner APIs ist ein notwendiger Vorläufer für die Stabilisierung
die Future APIs, die selbst ein notwendiger Vorläufer für die Stabilisierung der
async/await Syntax und Verschieben des gesamten asynchronen IO-Ökosystems futures 0.3
auf stabilen Rost.

cc @cramertj @RalfJung

T-libs finished-final-comment-period

Hilfreichster Kommentar

Ist jemand am Haken, um ein Nomicon-Kapitel über Futures zu schreiben? Es scheint unglaublich notwendig, wenn man bedenkt, wie subtil dies ist. Insbesondere denke ich, dass Beispiele, insbesondere Beispiele für fehlerhafte / fehlerhafte Implementierungen und wie sie nicht einwandfrei sind, aufschlussreich wären.

Alle 213 Kommentare

@rfcbot fcp zusammenführen

Teammitglied @withoutboats hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch den Rest der getaggten Teammitglieder:

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [x] @dtolnay
  • [] @sfackler
  • [x] @ohne Boote

Sorgen:

  • box-pinnned-vs-box-pin (https://github.com/rust-lang/rust/issues/55766#issuecomment-443371976)
  • Verbesserung der Dokumentation (https://github.com/rust-lang/rust/issues/55766#issuecomment-443367737)
  • Die Benennung von Unpin wurde durch https://github.com/rust-lang/rust/issues/55766#issuecomment -445074980 behoben
  • Selbstmethoden (https://github.com/rust-lang/rust/issues/55766#issuecomment-443368794)

Sobald die Mehrheit der Prüfer zustimmt (und höchstens 2 Genehmigungen ausstehen), tritt dies in die endgültige Kommentierungsfrist ein. Wenn Sie ein großes Problem entdecken, das zu keinem Zeitpunkt in diesem Prozess angesprochen wurde, melden Sie sich bitte!

In diesem Dokument finden Sie Informationen darüber, welche Befehle Teammitglieder mit Tags geben können.

Vielen Dank für die ausführliche Beschreibung hier @withoutboats! Ich war auch historisch verwirrt von den verschiedenen Garantien von Pin , und ich habe derzeit ein paar Fragen zu den APIs, die hier in Bezug auf die Sicherheitsgarantien stabilisiert werden. Um dies in meinem eigenen Kopf zu klären, dachte ich, ich würde versuchen, diese Dinge aufzuschreiben.

Beim Versuch, dies aufzuschreiben, renne ich immer wieder gegen eine Wand aus "Was ist Unpin ?" Ich bin irgendwie verwirrt von dem, was das ist und den verschiedenen Garantien, die damit verbunden sind. Können Sie noch einmal sagen, was es für T , Unpin nicht zu implementieren? Auch wenn Unpin eine sichere Eigenschaft ist, um es naiv zu implementieren, scheint es, als könnte es verwendet werden, um die unsicheren Garantien von Pin<T> leicht zu untergraben, aber mir fehlt sicherlich etwas

Wenn ich das richtig verstanden habe, ist jeder Typ, der nicht selbstreferenziell ist (dh kein Generator), Unpin

Es ist nicht nur Selbstreferenzialität, es gibt auch einige andere Anwendungsfälle für stabile Speicheradressen, die Pin ebenfalls unterstützen können. Sie sind jedoch relativ selten und weit voneinander entfernt.

Ich verstehe, dass die Implementierung von Unpin sicher ist, dass Sie durch die Implementierung eine Invariante verletzen können, die für anderen unsicheren Code erforderlich ist, den Sie geschrieben haben (entscheidend ist, dass nur Sie diesen unsicheren Code schreiben dürfen, kein externer Code kann sich darauf verlassen, ob Sie haben Unpin implementiert. Mit der sicheren API von Pin können Sie nichts tun, was zu Unklarheiten führen kann, unabhängig davon, ob Sie Unpin implementiert haben oder nicht. Wenn Sie sich für die Verwendung einiger unsicherer APIs von Pin , garantieren Sie, dass Sie Unpin nur dann implementieren, wenn dies sicher ist. Dies wird in Punkt 1 des obigen Abschnitts "Hinweise zum Fixieren und zur Sicherheit" behandelt.

Hm, ich verstehe Unpin immer noch nicht wirklich. Ich versuche zunächst nur zu verstehen, was es bedeutet, Unpin zu implementieren oder nicht zu treiben.

Zunächst ist es wahrscheinlich hilfreich zu wissen, welche Typen Unpin automatisch implementieren! Es wurde oben erwähnt, dass gängige Zeigertypen (Arc / Rc / Box / Referenzen) Unpin implementieren, aber ich denke, das ist es? Wenn dies ein automatisches Merkmal ist, bedeutet dies, dass ein Typ MyType automatisch Unpin implementiert, wenn er nur Zeiger enthält? Oder implementieren keine anderen Typen automatisch Unpin ?

Ich versuche immer wieder zusammenzufassen oder anzugeben, was Unpin Garantien und dergleichen sind, aber ich finde es wirklich schwierig, dies zu tun. Kann jemand helfen, indem er noch einmal wiederholt, was es bedeutet, Unpin zu implementieren und was es bedeutet, Unpin nicht zu implementieren?

Ich glaube, ich verstehe die Garantien von Pin<P> denen Sie aus keinem der Inline-Mitglieder von P::Target ausziehen können, aber ist das richtig?

@alexcrichton Vielen Dank für die Fragen. Ich bin sicher, dass die Pinning-APIs für Leute, die nicht Teil der Gruppe waren, die sich auf sie konzentriert, etwas verwirrend sein können.

Zunächst ist es wahrscheinlich hilfreich zu wissen, welche Typen Unpin automatisch implementieren!

Unpin ist eine automatische Eigenschaft wie Senden oder Synchronisieren, daher implementieren die meisten Typen sie automatisch. Generatoren und die Arten von asynchronen Funktionen sind !Unpin . Typen, die einen Generator oder einen asynchronen Funktionskörper enthalten könnten (mit anderen Worten Typen mit Typparametern), sind ebenfalls nicht automatisch Unpin sei denn, ihre Typparameter sind.

Die explizite Implikation für Zeigertypen besteht darin, Unpin auch wenn ihre Typparameter dies nicht sind. Der Grund dafür wird hoffentlich am Ende dieses Kommentars klarer.

Kann jemand helfen, indem er noch einmal wiederholt, was es bedeutet, Unpin zu implementieren und was es bedeutet, Unpin nicht zu implementieren?

Hier ist die Art der Grundidee der Pinning-APIs. Erstens, wenn ein Zeigertyp P ist, verhält sich Pin<P> wie P außer dass es nicht sicher ist, veränderlich dereferenziert zu werden, es sei denn, P::Target implementiert Unpin Pin<P> . Zweitens gibt es zwei grundlegende Invarianten, die unsicheren Code in Bezug auf Pin pflegen müssen:

  • Wenn Sie unsicher &mut P::Target von Pin<P> , dürfen Sie niemals P::Target bewegen.
  • Wenn Sie ein Pin<P> erstellen können, muss garantiert werden, dass Sie niemals einen nicht fixierten Zeiger auf die Daten erhalten, auf die der Zeiger zeigt, bis der Destruktor ausgeführt wird.

Dies alles impliziert, dass sich der Wert, auf den P zeigt, beim Erstellen eines Pin<P> nie wieder bewegt. Dies ist die Garantie, die wir für selbstreferenzielle Strukturen sowie für aufdringliche benötigen Sammlungen. Sie können diese Garantie jedoch abbestellen, indem Sie einfach Unpin für Ihren Typ implementieren.

Wenn Sie also Unpin für einen Typ implementieren, sagen Sie, dass der Typ keine zusätzlichen Sicherheitsgarantien von Pin - es ist möglich, die darauf zeigenden Zeiger gegenseitig zu dereferenzieren. Dies bedeutet, dass Sie sagen, dass der Typ nicht unbeweglich sein muss, um sicher verwendet zu werden.

Wenn Sie einen Zeigertyp wie Rc<T> verschieben, werden T nicht verschoben, da sich T hinter einem Zeiger befindet. In ähnlicher Weise wird durch das Fixieren eines Zeigers auf ein Rc<T> (wie in Pin<Box<Rc<T>> ) nicht T , sondern nur dieser bestimmte Zeiger. Aus diesem Grund kann alles, was seine Generika hinter einem Zeiger hält, Unpin implementieren, auch wenn ihre Generika dies nicht tun.

Wenn Unpin ein sicheres Merkmal ist, um es naiv zu implementieren, scheint es auch verwendet zu werden, um die unsicheren Garantien von Pin leicht zu untergraben, aber mir fehlt sicherlich etwas

Dies war einer der schwierigsten Teile der Pinning-API, und wir haben es zunächst falsch verstanden.

Unpin bedeutet "Selbst wenn etwas in einen Pin gesteckt wurde, ist es sicher, einen veränderlichen Verweis darauf zu erhalten." Es gibt heute ein weiteres Merkmal, das Ihnen den gleichen Zugriff ermöglicht: Drop . Wir haben also herausgefunden, dass Drop sicher ist und Unpin auch sicher sein muss. Untergräbt dies das gesamte System? Nicht ganz.

Um einen selbstreferenziellen Typ tatsächlich zu implementieren, wäre unsicherer Code erforderlich. In der Praxis interessieren sich nur die vom Compiler für Sie generierten selbstreferenziellen Typen: Generatoren und Zustandsautomaten für asynchrone Funktionen. Diese sagen ausdrücklich, dass sie Unpin nicht implementieren und dass sie keine Drop Implementierung haben. Sie wissen also, dass sie für diese Typen, sobald Sie eine Pin<&mut T> , dies tun werden Erhalten Sie niemals eine veränderbare Referenz, da es sich um einen anonymen Typ handelt, von dem wir wissen, dass er Unpin oder Drop nicht implementiert.

Das Problem tritt auf, wenn Sie eine Struktur haben, die einen dieser anonymen Typen enthält, wie z. B. einen zukünftigen Kombinator. Um von einem Pin<&mut Fuse<Fut>> zu einem Pin<&mut Fut> , müssen Sie eine "Stiftprojektion" durchführen. Hier können Probleme auftreten: Wenn Sie das Projekt an das zukünftige Feld eines Kombinators anheften und dann Drop für den Kombinator implementieren, können Sie ein Feld verlassen, das angeheftet werden soll.

Aus diesem Grund ist die Stiftprojektion unsicher! Um eine Pin-Projektion durchzuführen, ohne die Pinning-Invarianten zu verletzen, müssen Sie sicherstellen, dass Sie niemals mehrere Dinge tun, die ich im Stabilisierungsvorschlag aufgeführt habe.

Also existiert das tl; dr: Drop , also muss Unpin sicher sein. Dies ruiniert jedoch nicht das Ganze, sondern bedeutet nur, dass die Pin-Projektion unsafe beträgt und jeder, der ein Pin-Projekt durchführen möchte, eine Reihe von Invarianten aufrechterhalten muss.

Generatoren und Zustandsautomaten mit asynchroner Funktion. Diese sagen ausdrücklich, dass sie Unpin nicht implementieren und keine Drop-Implementierung haben. Sie wissen also, dass diese Typen, sobald Sie einen Pin <& mut T> haben, niemals eine veränderbare Referenz erhalten, weil sie es sind Ein anonymer Typ, von dem wir wissen, dass er Unpin oder Drop nicht implementiert.

Sollte eine asynchrone Zustandsmaschine nicht eine Drop Implementierung haben? Dinge, die sich auf dem "Stapel" der asynchronen Funktion befinden (was wahrscheinlich den Feldern in der Zustandsmaschine entspricht), müssen zerstört werden, wenn die asynchrone Funktion abgeschlossen oder abgebrochen wird. Oder passiert das anders?

Ich denke, was in diesem Zusammenhang wichtig ist, ist, ob ein impl Drop for Foo {…} -Element existiert, das Code mit &mut Foo ausführen würde, der zum Beispiel mem::replace , um das Foo zu "exfiltrieren" und zu verschieben

Dies ist nicht dasselbe wie "Tropfenkleber", der über ptr::drop_in_place aufgerufen werden kann. Tropfenkleber für einen bestimmten Foo -Typ ruft Drop::drop wenn er implementiert ist, und ruft dann rekursiv den Tropfenkleber für jedes Feld auf. Diese rekursiven Aufrufe beinhalten jedoch niemals &mut Foo .

Auch wenn ein Generator (und daher eine asynchrone Zustandsmaschine) über benutzerdefinierten Drop-Kleber verfügt, ist es nur das Löschen des richtigen Satzes von Feldern basierend auf dem aktuellen Status. Er verspricht jedoch, keines der Felder während des Ablegens zu verschieben.

Die Terminologie, die ich verwende (obwohl ich glaube, dass es keinen Standard gibt): "drop glue" ist das vom Compiler generierte rekursive Gehen von Feldern, wobei deren Destruktoren aufgerufen werden. "Drop-Implementierung" ist eine Implementierung des Merkmals Drop , und "Destruktor" ist die Kombination aus Drop-Kleber und Drop-Implementierung. Der Tropfenkleber bewegt nie etwas, daher kümmern wir uns nur um Tropfenimplementierungen.

Ist jemand am Haken, um ein Nomicon-Kapitel über Futures zu schreiben? Es scheint unglaublich notwendig, wenn man bedenkt, wie subtil dies ist. Insbesondere denke ich, dass Beispiele, insbesondere Beispiele für fehlerhafte / fehlerhafte Implementierungen und wie sie nicht einwandfrei sind, aufschlussreich wären.

@ Gankro Klar, du kannst mich dafür

Danke für die Erklärung an alle!

Ich persönlich bin super neu in Async Rust und den Pin-APIs, aber ich habe in den letzten Tagen ein bisschen damit herumgespielt (https://github.com/rust-lang-nursery/futures-rs/pull/1315 - wo Ich habe versucht, das Pinning für aufdringliche Sammlungen in asynchronem Code auszunutzen.

Während des Experimentierens habe ich einige Bedenken bezüglich dieser APIs bekommen:

  • Das Konzept des Fixierens scheint sehr schwer zu verstehen, selbst wenn man weiß, dass stabile Speicheradressen erforderlich sind. Einige Herausforderungen, vor denen ich stand:

    • Fehlermeldungen wie:

      > Innerhalb von impl core::future::future::Future+futures_core::future::FusedFuture ist das Merkmal std::marker::Unpin für std::marker::Pinned nicht implementiert

      Dieser klingt ziemlich rekursiv und widersprüchlich (warum sollte etwas, das angeheftet ist, nicht angeheftet werden?)

    • Wie kann ich in meiner Methode auf veränderbare Felder zugreifen, die einen Pin als Parameter verwenden? Es dauerte ein bisschen Suche, das Erlernen neuer Begriffe ( Pin Projektion) und der Versuch, die Methoden von pin-utils zu kopieren, bis ich zu einer Lösung kam, die zwei unsichere Methoden verwendet, um das zu tun, was ich brauchte .

    • Wie kann ich meine Zukunft tatsächlich in einer async -Methode und einem select -Fall nutzen? Es stellte sich heraus, dass ich pin_mut!() zum Futures-Stack brauchte. Was nicht wirklich ein Stapel ist, was es verwirrend macht.

    • Warum nimmt meine drop() -Methode jetzt wieder &mut self anstelle von Pin .

    • Insgesamt war es ein bisschen das Gefühl, zufällig unsichere Pinning-bezogene APIs zu verwenden, in der Hoffnung, Dinge zum Kompilieren zu bringen, was definitiv nicht die ideale Situation sein sollte. Ich vermute, andere könnten versuchen, ähnliche Dinge zu tun.

  • Das Konzept verbreitet sich stark in der gesamten Codebasis, was viele Leute dazu zu zwingen scheint, zu verstehen, was Pin und Unpin tatsächlich sind. Dies scheint ein bisschen ein Leck an Implementierungsdetails für einige speziellere Anwendungsfälle zu sein.
  • Pin scheint etwas mit der Lebensdauer zu tun zu haben, aber auch etwas orthogonal. Grundsätzlich sagt uns ein Pin, dass wir möglicherweise ein Objekt mit genau derselben Adresse wieder sehen, bis drop() aufgerufen wird. Dies gibt ihm eine Art virtuelle Lebensdauer zwischen dem aktuellen Bereich und 'static . Es scheint verwirrend, zwei Konzepte zu haben, die verwandt, aber immer noch völlig unterschiedlich sind. Ich fragte mich, ob es von so etwas wie 'pinned modelliert werden kann.

Beim Kompilieren habe ich oft an C ++ gedacht, wo die meisten dieser Probleme vermieden werden: Typen können durch Löschen des Verschiebungskonstruktors und des Zuweisungsoperators als unbeweglich deklariert werden. Wenn ein Typ nicht beweglich ist, sind es auch keine Typen, die diesen Typ enthalten. Dabei fließen die Eigenschaft und die Anforderungen nativ durch die Typhierarchie und werden vom Compiler überprüft, anstatt die Eigenschaft innerhalb einiger Aufrufe weiterzuleiten (nicht alle, z. B. nicht drop() ). Ich denke, das ist viel einfacher zu verstehen. Aber vielleicht nicht anwendbar auf Rust, da Future s derzeit oft verschoben werden müssen, bevor etwas anfängt, sie abzufragen? Auf der anderen Seite könnte dies mit einer neuen Definition mit diesem Merkmal oder der Trennung von Bewegungs- und Abfragephase behoben werden.

In Bezug auf Alex Unpin Kommentar: Ich verstehe langsam, was Unpin bedeutet, aber ich stimme zu, dass es schwer zu verstehen ist. Vielleicht hilft ein anderer Name, aber ich kann momentan keinen prägnanten finden. Etwas entlang ThingsInsideDontBreakIfObjectGetsMoved , DoesntRequireStableAddress , PinDoesntMatter usw.

Was ich noch nicht vollständig verstanden habe, ist, warum es nicht für alle Typen sicher sein kann, &mut self von Pin<&mut self> zu bekommen. Wenn Pin nur bedeuten würde, dass die Adresse des Objekts selbst stabil ist, sollte dies für die meisten typischen Rust-Typen gelten. Es scheint, dass in Pin ein weiteres Problem integriert ist, nämlich dass auch selbstreferenzielle Typen darin nicht kaputt gehen. Für diese Typen ist es immer unsicher, sie nach einem Pin manipulieren. Aber ich denke, in den meisten Fällen werden diese vom Compiler generiert, und Unsicherheit oder sogar rohe Zeiger sollten in Ordnung sein. Für einen manuell zukünftigen Kombinator, der angeheftete Anrufe an Unterfelder weiterleiten muss, müssten natürlich unsichere Aufrufe vorhanden sein, um Stecknadeln für die Felder zu erstellen, bevor diese abgefragt werden. Aber in diesem Fall sehe ich die Unsicherheit eher in Bezug auf den Ort, an dem sie tatsächlich wichtig ist (ist die PIN noch gültig?), Als auf den Zugriff auf andere Felder, für die sie überhaupt keine Rolle spielt.

Ein anderer Gedanke, den ich hatte, ist, ob es möglich ist, ein einfacheres System zu erhalten, wenn einige Einschränkungen angewendet werden. Zum Beispiel, wenn Dinge, die fixiert werden müssen, nur innerhalb asynchroner Methoden und nicht mit zukünftigen Kombinatoren verwendet werden könnten. Vielleicht könnte das die Dinge zu einem von Pin oder Unpin vereinfachen. In Anbetracht dessen, dass für viele Code-Async / Wait-Methoden mit Select / Join-Unterstützung für Kombinatoren möglicherweise günstig ist, ist dies möglicherweise nicht der größte Verlust. Aber ich habe nicht wirklich genug darüber nachgedacht, ob dies wirklich hilft.

Positiv zu vermerken ist, dass es ziemlich cool ist und dringend benötigt wird, asynchronen / wartenden Code mit "Stack" -Liehen zu schreiben! Die Möglichkeit, Pinning für andere Anwendungsfälle wie aufdringliche Sammlungen zu verwenden, trägt sowohl zur Leistung als auch zu Zielen wie eingebetteten Systemen oder Kerneln bei. Ich freue mich sehr auf eine Lösung.

Kleinere Angaben zum Stabilisierungsbericht:

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

Ich vermute, das ist tatsächlich ein unsafe fn ?

@ Matthias247 Sie können & mut T nicht sicher von Pin<P<T>> wenn T nicht Unpin ist, weil Sie dann mem::swap könnten, um T aus dem Pin zu entfernen, was den Zweck des Fixierens von Dingen zunichte machen würde.

Eine Sache, die ich mir nur schwer erklären kann, ist, was Future grundlegend von anderen Merkmalen unterscheidet, so dass Pin Teil seiner API sein muss. Ich meine, ich weiß intuitiv, dass es daran liegt, dass Async / Warten ein Fixieren erfordert, aber sagt das etwas spezielles über Futures aus, das sich beispielsweise von Iteratoren unterscheidet?

Könnte poll take & mut self und Future nur für Pin<P> Typen oder Typen implementieren, die Unpin sind?

Wie kann ich in meiner Methode auf veränderbare Felder zugreifen, die einen Pin als Parameter verwenden?

Dies lässt mich tatsächlich fragen, ob es ein paar fehlende Methoden für Pin gibt.

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>
}

Diese können im Makro pin_utils::unsafe_unpinned! .

Ich versuche herauszufinden, warum dieses Makro behauptet, unsicher zu sein. Wenn wir !Unpin und ein Unpin -Feld haben, warum wäre es dann unsicher, in dieses Feld zu projizieren?

Die einzige Situation, in der ich sehe, dass es nicht in Ordnung ist, besteht darin, einen benutzerdefinierten !Unpin -Typ zu implementieren, einen rohen Zeiger auf ein Unpin -Feld von self zu nehmen (und sich darauf zu verlassen, dass es eine stabile Adresse hat / auf das zeigt gleiche Instanz), dann ein &mut an dasselbe Feld erfassen und an eine externe Funktion übergeben. Dies scheint unter die gleiche Überlegung zu fallen, warum die Implementierung von Unpin sicher ist. Wenn Sie jedoch einen rohen Zeiger auf ein Feld von !Unpin setzen, können Sie einen Teil des Safes nicht mehr aufrufen APIs.

Um die vorherige Situation sicherer zu machen, könnte es einen Wrapper geben, der auf Pinned zum Markieren basiert, wobei normalerweise Unpin Felder einer Struktur tatsächlich !Unpin anstatt nur Pinned hinzuzufügen

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 { ... }
}

Dies alles scheint abwärtskompatibel mit der aktuellen API zu sein. Es könnte einige der Unsicherheiten von futures-rs Kombinatoren beseitigen (z. B. würde es einen sicheren Zugriff auf zusätzliche Felder wie dieses ermöglichen ), ist jedoch für die aktuellen Verwendungsfälle nicht erforderlich. Wir könnten wahrscheinlich mit einigen APIs wie diesen experimentieren, um benutzerdefinierte !Unpin -Typen (wie aufdringliche Sammlungen) zu implementieren und sie später hinzuzufügen.

@ Nemo157 Diese Kartenfunktionen sind unsicher, da ich mem::swap auf dem &mut T der Funktion übergeben kann, bevor ich ein &mut U . (Nun, der Veränderliche ist, der Unveränderliche ist vielleicht nicht unsicher.)

BEARBEITEN: Auch das Pin-Utils-Makro ist anders, unsafe_unpinned nicht damit zu tun, dass der Zieltyp Unpin , sondern nur eine "nicht fixierte Projektion" - eine Projektion auf &mut Field . Es ist sicher zu verwenden, solange Sie niemals ein Projekt an dieses Feld anheften, selbst wenn das Feld !Unpin .

Eine Sache, die ich mir nur schwer erklären kann, ist, was Future grundlegend von anderen Merkmalen unterscheidet, so dass Pin Teil seiner API sein muss. Ich meine, ich weiß intuitiv, dass es daran liegt, dass Async / Warten ein Fixieren erfordert, aber sagt das etwas spezielles über Futures aus, das sich beispielsweise von Iteratoren unterscheidet?

Es gibt keinen theoretischen Unterschied, aber es gibt pragmatische. Zum einen ist Iterator stabil. Aber wenn wir nicht etwas sehr Kluges herausfinden, können Sie niemals eine for -Schleife über einen selbstreferenziellen Generator ohne doppelte Indirektion ausführen, auch wenn dies ohne diese absolut sicher sein sollte ( weil die for-Schleife den Generator verbraucht und niemals bewegt).

Ein weiterer wichtiger pragmatischer Unterschied besteht darin, dass die Codemuster zwischen Iterator und Future sehr unterschiedlich sind. Sie können nicht 10 Minuten vergehen, ohne in Zukunft einen Wartepunkt ausleihen zu wollen. Aus diesem Grund werden bei Futures 0.1 diese Arc überall angezeigt, sodass Sie dieselbe Variable in zwei verschiedenen verwenden können and_then Anrufe. Aber wir sind ziemlich weit gekommen, ohne in der Lage zu sein, selbstreferenzielle Iteratoren auszudrücken. Es ist einfach nicht so wichtig für einen Anwendungsfall.

Diese Kartenfunktionen sind unsicher, da ich mem::swap auf dem &mut T der Funktion übergeben kann, bevor ich ein &mut U

Ah, verdammt, habe diesen Teil vergessen: Stirnrunzeln:

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

Sollten diese als as_pinned_mut und into_pinned_ref , um zu vermeiden, dass sie mit Methoden verwechselt werden, die tatsächlich Referenzen zurückgeben?

Bemerkenswerte Implementierungen von Unpin in std:

Wir haben auch impl<P> Unpin for Pin<P> {} . Für die Typen, die wir verwenden, hat dies keine Wirkung und es scheint ein bisschen sicherer?
Egal, das hast du auf deiner Liste. ;)

Drop-Garantie

Ich denke, wir müssen die Drop Garantie vor der Stabilisierung kodifizieren, sonst wird es zu spät sein:

Es ist illegal, die Speicherung eines angehefteten Objekts (das Ziel einer Pin Referenz) ungültig zu machen, ohne drop für das Objekt aufzurufen.

"Ungültig" kann hier "Freigabe", aber auch "Umnutzung" bedeuten: Wenn Sie x von Ok(foo) in Err(bar) ändern, wird der Speicher von foo ungültig .

Dies hat zum Beispiel zur Folge, dass es illegal wird, die Zuweisung von Pin<Box<T>> , Pin<Rc<T>> oder Pin<Arc<T>> ohne vorher drop::<T> .

Umnutzung von Deref , DerefMut

Ich fühle mich immer noch etwas unwohl darüber, wie dies das Merkmal Deref wiederverwendet, um "dies ist ein kluger Zeiger" zu bedeuten. Wir verwenden Deref für andere Dinge, z. B. für "Vererbung". Das mag ein Anti-Muster sein, aber es wird immer noch ziemlich häufig verwendet und ist ehrlich gesagt nützlich. : D.

Ich denke, dies ist jedoch kein Problem der Solidität.

Unpin verstehen

Schamloser Plug: Ich habe zwei Blog-Beiträge dazu geschrieben, die helfen können oder auch nicht, je nachdem, wie gerne Sie (halb-) formale Syntax lesen. ;) Die APIs haben sich seitdem geändert, die Grundidee jedoch nicht.

Sichere Stiftprojektionen

@ Matthias247 Ich denke, ein Problem, auf das Sie stoßen, ist, dass das Erstellen von Abstraktionen, die das Fixieren beinhalten, derzeit fast immer unsicheren Code erfordert. Die Verwendung dieser Abstraktionen ist in Ordnung, aber z. B. funktioniert die Definition eines zukünftigen Kombinators in sicherem Code nicht. Der Grund dafür ist, dass "Pin-Projektionen" Einschränkungen aufweisen, die wir nur mit Compiler-Änderungen sicher überprüfen konnten. Zum Beispiel fragst du

Warum nimmt meine drop () -Methode jetzt wieder & mut self anstelle eines Pins?

Nun, drop() ist alt - es existiert seit Rust 1.0 - also können wir es nicht ändern. Wir würden es lieben, wenn es nur Pin<&mut Self> dauern würde, und dann könnten Unpin Typen ihre &mut wie sie es jetzt tun, aber das ist eine nicht abwärtskompatible Änderung.

Um das Schreiben zukünftiger Kombinatoren sicher zu machen, benötigen wir sichere Pin-Projektionen. Dazu müssen wir den Compiler ändern. Dies ist in einer Bibliothek nicht möglich. Wir könnten es fast in einem derive proc_macro tun, außer dass wir eine Möglichkeit brauchen würden, um zu behaupten, "dieser Typ hat keine Implementierung von Drop oder Unpin ".

Ich denke, es wäre die Mühe wert, herauszufinden, wie man eine so sichere API für Pin-Projektionen erhält. Dies muss jedoch die Stabilisierung nicht blockieren: Die API, die wir hier stabilisieren, sollte vorwärtskompatibel mit sicheren Stiftprojektionen sein. ( Unpin möglicherweise ein langes Element werden, um die obige Behauptung zu implementieren, aber das scheint nicht schlecht zu sein.)

Pinning ist nicht ! Bewegen

Ich habe oft an C ++ gedacht, wo die meisten dieser Probleme vermieden werden: Typen können durch Löschen des Verschiebungskonstruktors und des Zuweisungsoperators als unbeweglich deklariert werden. Wenn ein Typ nicht beweglich ist, sind es auch keine Typen, die diesen Typ enthalten.

Es gab mehrere Versuche, unbewegliche Typen für Rust zu definieren. Es wird sehr schnell sehr kompliziert.

Eine wichtige Sache zu verstehen ist , dass Pinning nicht unbewegliche Arten liefern! Alle Typen bleiben in Rust beweglich. Wenn Sie ein T , können Sie es beliebig verschieben. Anstelle von nicht verschiebbaren Typen verwenden wir die Fähigkeit von Rust, neue gekapselte APIs zu definieren, da wir einen Zeigertyp definieren, von dem Sie nicht ausgehen können . Die ganze Magie liegt im Zeigertyp ( T Pin<&mut T> ), nicht im Pointee ( T ). Es gibt keine Möglichkeit für einen Typ zu sagen "Ich kann mich nie bewegen". Es gibt jedoch eine Möglichkeit für einen Typ zu sagen: " Wenn ich festgesteckt wurde , bewege mich nicht erneut." Ein MyFuture , das ich besitze, ist also immer beweglich, aber Pin<&mut MyFuture> ist ein Zeiger auf eine Instanz von MyFuture , die nicht mehr verschoben werden kann .

Dies ist ein subtiler Punkt, und wir sollten wahrscheinlich einige Zeit damit verbringen, die Dokumente hier zu konkretisieren, um dieses sehr häufige Missverständnis zu vermeiden.

Aber wir sind ziemlich weit gekommen, ohne in der Lage zu sein, selbstreferenzielle Iteratoren auszudrücken. Es ist einfach nicht so wichtig für einen Anwendungsfall.

Dies liegt daran, dass bisher alle Iteratoren mit einem Typ und einem impl Iterator for … -Block definiert wurden. Wenn der Status zwischen zwei Iterationen beibehalten werden muss, bleibt keine andere Wahl, als ihn in Feldern des Iteratortyps zu speichern und mit &mut self .

Selbst wenn dies nicht die Hauptmotivation für die Aufnahme von Generatoren in die Sprache ist, wäre es sehr schön, irgendwann die Generatorsyntax yield zu können, um etwas zu definieren, das mit einem for loop. Sobald dies der Fall ist, wird eine Kreditaufnahme über yield da dies für Generator-Iteratoren genauso wichtig ist wie für Generator-Futures.

( Unpin möglicherweise ein langes Element werden, um die obige Behauptung zu implementieren, aber das scheint nicht schlecht zu sein.)

Unpin und Pin bereits lang sein, um sichere unbewegliche Generatoren zu unterstützen.

Ok danke für die Erklärungen alle! Ich stimme @Gankro zu, dass ein Kapitel im Nomicon-Stil über Pin und dergleichen hier äußerst nützlich wäre. Ich vermute, dass es eine Menge Entwicklungsgeschichte gibt, warum es verschiedene sichere Methoden gibt und warum unsichere Methoden unsicher sind und so.

Um mir zu helfen, dies zu verstehen, wollte ich noch einmal versuchen aufzuschreiben, warum jede Funktion sicher ist oder warum sie unsafe . Mit dem Verständnis von oben habe ich alles Folgende, aber hier und da gibt es ein paar Fragen. Wenn andere mir helfen können, dies auszufüllen, wäre das großartig! (oder helfen Sie herauszufinden, wo mein Denken falsch ist)

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

    • Dies ist eine sichere Methode, um Pin<P> zu konstruieren, und zwar während der Existenz von
      Pin<P> bedeutet normalerweise, dass P::Target nie wieder in der verschoben wird
      Programm, P::Target implementiert Unpin das "die Garantien von
      Pin nicht mehr halten ". Infolgedessen ist die Sicherheit hier, weil es keine gibt
      garantiert zu halten, damit alles wie gewohnt ablaufen kann.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • Im Gegensatz zur vorherigen Funktion ist diese Funktion unsafe da P dies nicht tut
      Implementieren Sie unbedingt Unpin , also muss Pin<P> die Garantie dafür einhalten
      P::Target wird sich nie wieder bewegen, nachdem Pin<P> erstellt wurde.

    Ein trivialer Weg, um diese Garantie zu verletzen, wenn diese Funktion sicher ist, sieht aus
    mögen:

    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
    }
    

    Folglich ist es Sache des Benutzers, zu garantieren, dass Pin<P> tatsächlich bedeutet
    P::Target wird nach dem Bau nie wieder verschoben, also sind es unsafe !

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

    • Angesichts von Pin<P> wir die Garantie, dass sich P::Target niemals bewegen wird. Das ist
      Teil des Vertrages von Pin . Infolgedessen bedeutet es trivial, dass
      &P::Target , ein weiterer "intelligenter Zeiger" auf P::Target wird dasselbe liefern
      Garantie, so dass &Pin<P> sicher in Pin<&P::Target> .

    Dies ist eine generische Methode, um von Pin<SmartPointer<T>> auf Pin<&T>

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

    • Ich glaube, die Sicherheit hier ist ungefähr die gleiche wie bei den oben genannten as_ref .
      Wir verteilen keinen veränderlichen Zugriff, nur einen Pin , also kann nichts einfach sein
      noch verletzt.

    Frage : Was ist mit einem "böswilligen" DerefMut Impl? Dies ist ein sicherer Weg
    um ein vom Benutzer bereitgestelltes DerefMut anzurufen, das &mut P::Target nativ kisten kann,
    vermutlich erlaubt es ihm, es auch zu modifizieren. Wie ist das sicher?

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

    • Frage : Angesichts von Pin<P> (und der Tatsache, dass wir nichts darüber wissen
      Unpin ), sollte dies nicht garantieren, dass sich P::Target niemals bewegt? Wenn wir können
      Initialisieren Sie es mit einem weiteren P::Target . Sollte dies nicht unsicher sein?
      Oder hängt das irgendwie mit Destruktoren und all dem zusammen?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • Dies ist eine unsafe -Funktion, daher lautet die Hauptfrage hier: "Warum ist das nicht so?"
      safe "? Ein Beispiel für die Verletzung von Garantien, wenn dies sicher wäre, sieht aus wie:

    ...

    Frage :: Was ist hier das Gegenbeispiel? Wenn dies sicher wäre, was dann?
    das Beispiel, das zeigt, dass die Garantien von Pin verletzt werden?

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

    • Die Garantie von Pin<&T> bedeutet, dass sich T niemals bewegen wird. Rückgabe von &T
      erlaubt keine Mutation von T , daher sollte es sicher sein, dies während der Aufrechterhaltung zu tun
      diese Garantie.

    Ein "Vielleicht Gotcha" hier ist die innere Veränderlichkeit, was wäre, wenn T wären
    RefCell<MyType> ? Dies verstößt jedoch nicht gegen die Garantien von
    Pin<&T> da die Garantie nur für T als Ganzes gilt, nicht für die
    Innenfeld MyType . Während sich die innere Veränderlichkeit bewegen kann
    intern kann es grundsätzlich immer noch nicht die gesamte Struktur hinter a bewegen
    & Referenz.

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

    • Pin<&mut T> bedeutet, dass sich T niemals bewegen wird. Infolgedessen bedeutet es Pin<&T>
      bietet die gleiche Garantie. Sollte bei dieser Konvertierung kein großes Problem sein,
      Es sind meistens Schalttypen.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> bedeutet, dass sich T niemals bewegen darf, also ist dies trivial unsafe
      weil Sie mem::replace für das Ergebnis verwenden können, um T (sicher) zu verschieben. Das
      unsafe hier "obwohl ich dir &mut T gebe, darfst du das nie
      bewege T ".
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>

    • Ich denke, die unsafe hier sind im Grunde mindestens die gleichen wie oben, wir sind
      sicheres Verteilen von &mut T (kann keinen unsicheren Verschluss erfordern), was möglich ist
      einfach mit mem::replace . Es gibt wahrscheinlich auch andere Unsicherheiten hier
      mit der Projektion, aber es scheint vernünftig, dass es zumindest unsicher ist
      deswegen.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • Durch die Implementierung von Unpin sagt ein Typ " Pin<&mut T> hat keine Garantien, es ist
      Nur ein Newtype-Wrapper von &mut T ". Infolgedessen ohne Garantie dafür
      Halten Sie, wir können &mut T sicher zurückgeben
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • Dies kann sicher mit as_ref gefolgt von get_ref implementiert werden
      Die Sicherheit dieses Geräts ergibt sich aus dem Obigen.
  • impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }

    • Dies kann sicher mit as_mut gefolgt von get_mut implementiert werden
      Die Sicherheit dieses Geräts ergibt sich aus dem Obigen.
  • impl<T: ?Sized> Unpin for Box<T> (und andere zeigerbezogene Implementierungen)

    • Wenn keine anderen Maßnahmen ergriffen würden, würde Box<T> das Merkmal Unpin implementieren
      Nur wenn T Unpin implementiert hat. Diese Implementierung hier kodifiziert das sogar
      Wenn T Unpin T explizit nicht implementiert, implementiert Unpin Box<T> Unpin .

    Frage : Was ist ein Beispiel dafür, was dies grundlegend befähigt?

    Zum Beispiel, was für ein Safe sonst unsafe erfordern könnte, wenn dies impliziert

    hat nicht existiert.


@ Matthias247 Sie können & mut T nicht sicher von Pin bekommen

> Wenn T nicht Unpin ist, weil Sie dann mem :: swap verwenden könnten, um T aus dem Pin zu entfernen, was den Zweck des Fixierens von Dingen zunichte machen würde.

Vielen Dank! Ja, da swap keine unsichere Methode ist, ist dies offensichtlich problematisch. Aber konnte , dass durch Zugabe eines fixiert werden Unpin gebunden swap() ? Da der gesamte bisherige Code Unpin oder ohnehin unsicher ist, sollte dies nichts beschädigen.

Ich denke, eines der Dinge, die mich immer noch am meisten verwirren, ist, dass Pin<T> mehrere Garantien kodifiziert: Dass die Adresse des T stabil ist, sowie einige Garantien über seinen internen Zustand (in einigen ist es eingefroren / unveränderlich Situationen, aber auch nicht wirklich).

Es schien mir, als würde es vorzuziehen sein, den unsicheren Code / die unsicheren Projektionen nur an die Stellen zu verschieben, an denen weitere Anrufe Pin Basis von poll auf Feldern), um diese unsicheren Projektionen in zu bearbeiten alle Fälle. Jetzt wurde mir jedoch auch klar, dass es ein anderes Problem gibt: Code, der Zugriff auf die veränderbare Referenz hat, kann das Feld frei bewegen, und wenn drop() dann sicher in diesem Feld aufgerufen wird, kann es aufgrund dessen brechen Adresse wird woanders gespeichert. Um dies zu beheben, wäre die drop(Pin<T>) Überlastung erforderlich, über die gesprochen wurde.

@RalfJung Danke für die Erklärungen! Ich stimme der Tatsache zu, dass ich auf jeden Fall versucht habe, etwas Unsicheres im Allgemeinen zu tun, und daher sollte es in Ordnung sein, das zusätzliche Verständnis von mir zu verlangen. Ich mache mir mehr Sorgen um Leute, die allgemein sicherere zukünftige Kombinatoren schreiben wollen, aber jetzt auch mit all diesen Begriffen konfrontiert werden könnten. Wenn sie Kombinatoren schreiben können, ohne Unpin und Pin-Projektionen überhaupt verstehen zu müssen, und dann nur Kombinatoren erhalten, die reduziert arbeiten (nur bei Unpin Futures), scheint dies vorzuziehen. Da ich es nicht ausprobiert habe, kann ich nicht sagen, ob es derzeit der Fall ist. Ich denke, man muss immer noch mindestens Unpin Grenzen manuell hinzufügen.

Ich verstehe auch die Tatsache, dass sich nicht bewegliche Typen vom Stifttyp unterscheiden. Derzeit konzentriere ich mich jedoch mehr auf Anwendungsfälle als auf den Unterschied. Und für den Anwendungsfall von aufdringlichen Sammlungen funktionieren nicht bewegliche Typen sehr gut, ohne zu viel Komplexität einzuführen. Für Futures wäre natürlich etwas Forschung erforderlich, da der Weg von einem beweglichen zu einem nicht beweglichen Typ fehlt. Wenn dieser Weg nicht ergonomischer wäre als die Pin-APIs, gäbe es auch keinen Gewinn.

Das Hinzufügen von T: Unpin zu mem::swap würde bedeuten, dass es bei bestimmten Typen nicht verwendet werden kann, selbst wenn sie sich nicht in einem Pin befinden.

Aber könnte das durch Hinzufügen eines an swap () gebundenen Unpin behoben werden? Da der gesamte bisherige Code Unpin sein sollte oder ohnehin unsicher ist, sollte dies nichts beschädigen.

Das würde den gesamten generischen Code zerstören: Wenn Sie eine Funktion in den heutigen stabilen Rost für einige T ohne Einschränkungen schreiben, können Sie swap darauf aufrufen. Das muss weiter funktionieren.

Nicht bewegliche Typen funktionieren sehr gut, ohne zu viel Komplexität einzuführen.

Niemand hat einen Weg aufgezeigt, Rust nicht bewegliche Typen auf abwärtskompatible Weise hinzuzufügen wesentlich übersteigt. Einige dieser alten Vorschläge und Diskussionen finden Sie im RFC-Repo. Ein Beispiel finden Sie unter https://github.com/rust-lang/rfcs/pull/1858 .

Der RFC unter https://github.com/rust-lang/rfcs/pull/2349 sowie die hier beginnende Blogserie des Bootes sollen Ihnen dabei helfen, Hintergrundinformationen und einen Eindruck von den anderen in Betracht gezogenen Designs zu vermitteln. (Beachten Sie auch die Daten, dieser Entwurf ist seit fast 10 Monaten in Arbeit!)

Auch mem :: swap ist ein roter Hering, weil es überhaupt keine interessante Funktion ist. Es ist buchstäblich nur

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

@Gankro verwendet es keinen unsicheren Code? Afaik ist es nicht möglich, das wörtlich zu schreiben.

Bearbeiten: Ich denke, eine andere Möglichkeit, darüber nachzudenken, besteht darin, dass das Hinzufügen von T: Unpin zu mem::swap die Definition von Sicherheit auf Sprachebene tatsächlich ändert. Es würde alle mycrate::swap fns in freier Wildbahn brechen.

Das Hinzufügen von T: Unpin zu mem :: swap würde bedeuten, dass es für bestimmte Typen nicht verwendet werden kann, selbst wenn sie sich nicht in einem Pin befinden.

Wenn Unpin automatisch abgeleitet wird (auf die gleiche Weise wie Sync / Send ), dachte ich, dass dies kein Problem sein sollte.

Das würde den gesamten generischen Code zerstören: Wenn Sie eine Funktion in den heutigen stabilen Rost für einige T ohne Einschränkungen schreiben, können Sie Swap darauf aufrufen. Das muss weiter funktionieren.

Aber das ist offensichtlich. Ich habe nicht darüber nachgedacht, dass Merkmalsgrenzen in Rust explizit propagiert werden müssen.

Niemand hat einen Weg aufgezeigt, Rust nicht bewegliche Typen auf abwärtskompatible Weise hinzuzufügen, ohne dass die Komplexität die hier zur Stabilisierung vorgeschlagenen wesentlich übersteigt. Einige dieser alten Vorschläge und Diskussionen finden Sie im RFC-Repo. Ein Beispiel finden Sie unter rust-lang / rfcs # 1858.

Danke, ich werde ein bisschen mehr über die vorherigen Arbeiten dazu lesen, wenn ich etwas Zeit finde. Offensichtlich sind bereits viele Gedanken und Anstrengungen in diese Sache geflossen, und ich möchte die Dinge auf keinen Fall blockieren. Ich wollte nur meine Bedenken und Fragen äußern, wenn ich versuchte, damit zu arbeiten.

@ Matthias247

Wenn Unpin automatisch abgeleitet wird (auf die gleiche Weise wie Sync / Send), dachte ich, dass dies kein Problem sein sollte.

Ich bin mir nicht sicher, ob ich klar war. Um dies zu verdeutlichen, ist es absolut sicher, sich in einem !Unpin -Typ zu bewegen, und daher absolut sicher, sie um mem::swap herum zu bewegen. Es ist nur unsicher, einen Typ T: !Unpin nach dem Fixieren zu verschieben, dh innerhalb eines Pin<P<T>> .

Beispielsweise können Sie im asynchronen / wartenden Code beliebig viele Futures verschieben, die von einer asynchronen Funktion zurückgegeben werden. Sie hören erst auf, sie bewegen zu können, wenn Sie sie pin_mut! haben oder sie in ein Pin<Box<..>>> oder so weiter legen.

Ich habe einige verschiedene Fragen dazu:

  1. Angesichts der Bedeutung von Pin<T> für async und für Generatoren und des Potenzials für Unklarheiten bei der Interaktion mit anderen Teilen von Rust (z. B. swap & replace ) gibt es keine Die hier vorgeschlagene Variante der Pinning-API wurde @jhjourdan oder @RalfJung).

  2. Gibt diese API neue Garantien für die abstrakte Maschinen- / Betriebssemantik von Rust? Dh, wenn wir den Anwendungsfall als Unterstützungsmechanismus für async & await / Generatoren vergessen, könnten wir dies in eine Kiste im Ökosystem stecken und es würde angesichts der aktuellen Garantien, die wir haben, einfach funktionieren geben?

  3. Welche Art von APIs oder Typsystem-Ergänzungen werden durch die Stabilisierung der Pinning-API unmöglich? (Diese Frage ist eine Erweiterung von 2.)

  4. Welche Möglichkeiten bietet die zu stabilisierende API im Hinblick auf die Weiterentwicklung einer Sprache, die vom Typ &pin T bereitgestellt wird, um die Feldprojektion und dergleichen zu verbessern (was mit der vorgeschlagenen API nicht besonders gut zu sein scheint).

Ich habe einige verschiedene Notizen:

  1. Die Dokumentation in der Standardbibliothek scheint recht spärlich. :(

  2. Ich stimme anderen zu, dass das Pinning-Konstrukt ziemlich mental anstrengend / komplex ist.

  3. Das Beispiel Unmovable in der Dokumentation scheint zu kompliziert und beinhaltet unsafe ; das scheint nicht optimal. Mit einer schrittweisen Initialisierung, wie sie in einem RFC-Entwurf in der Sprache ausgeführt wird (dh die NLL verbessert), könnten wir stattdessen Folgendes erhalten:

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.

Dies beinhaltet null unsichere und es ist für den Benutzer ziemlich einfach, damit umzugehen.

Darüber hinaus bieten die Standard-APIs keine sichere Möglichkeit, Objekte an den Stapel anzuheften.
Dies liegt daran, dass es keine Möglichkeit gibt, dies mithilfe einer Funktions-API sicher zu implementieren.

Was ist mit API wie dieser?

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)
}

Ich habe die Entwicklung der Pinning-APIs nicht zu genau verfolgt, daher hätte dies an anderer Stelle erwähnt oder erklärt werden können, und ich konnte es nicht finden. Entschuldigung, wenn dies der Fall ist:

Der Typ Pinned ist eine ZST, die Unpin nicht implementiert. es erlaubt dir
Unterdrücken Sie die automatische Implementierung von Unpin auf Stable, wobei !Unpin impliziert
wäre noch nicht stabil.

Gibt es irgendwo eine Erklärung dafür, warum !Unpin Impls nicht stabil gemacht werden konnten?

Es ist nur sicher, wenn Foo (der enthaltende Typ) nicht repr (verpackt) ist,
weil dies dazu führt, dass Felder verschoben werden, um sie neu auszurichten.

Bedeutet gepackt, dass Felder dynamisch verschoben werden können? Das ist ein bisschen beängstigend. Sind wir sicher, dass llvm unter anderen Umständen niemals Code zum Verschieben von Feldern generiert? Ebenso ist es möglich, dass angeheftete Werte auf dem Stapel von llvm verschoben werden?

Trotz der Subtilität scheint dies eine wirklich schöne API zu sein. Gute Arbeit!

@Centril Ralf hat in seinem Blog über das Fixieren geschrieben.

Die Pin-APIs enthielten überhaupt keine Sprachänderungen und wurden vollständig in der Standardbibliothek unter Verwendung bereits vorhandener Sprachfunktionen implementiert. Es hat keine Auswirkungen auf Rust the language und schließt keine anderen Sprachfunktionen aus.

Pin ist wirklich nur eine clevere Implementierung einer der wertvollsten und klassischsten Funktionen von Rust: Die Möglichkeit, Invarianten in eine API einzuführen, indem ein Teil der API unsafe markiert wird. Pin umschließt einen Zeiger, um eine Operation ( DerefMut ) unsicher zu machen. Dies erfordert, dass Personen, die dies tun, bestimmte Invarianten einhalten (damit sie nicht aus der Referenz herausgehen) und anderen Code zulassen davon ausgehen, dass dies niemals eintreten wird. Ein ähnliches, viel älteres Beispiel für denselben Trick ist String , was es unsicher macht, Nicht-UTF8-Bytes in eine Zeichenfolge einzufügen, sodass anderer Code davon ausgehen kann, dass alle Daten in String sind UTF8.

Gibt es irgendwo eine Erklärung dafür, warum! Unpin-Geräte konnten nicht stabilisiert werden?

Negative Impls sind derzeit instabil und stehen in keinem Zusammenhang mit diesen APIs.

Negative Impls sind derzeit instabil.

🤦‍♂️ Das macht Sinn, hätte ein bisschen länger darüber nachdenken sollen. Vielen Dank.

@alexcrichton Das ist eine großartige Analyse dieser API. Wir sollten versuchen, sie an einem besseren Ort zu erhalten als ein Kommentar, der verloren geht!

Einige Kommentare:

as_mut : Frage: Was ist mit einem "böswilligen" DerefMut-Gerät? Dies ist ein sicherer Weg
um einen vom Benutzer bereitgestellten DerefMut aufzurufen, der & mut P :: Target nativ erstellt,
vermutlich erlaubt es ihm, es auch zu modifizieren. Wie ist das sicher?

Wenn Sie new_unchecked aufrufen, machen Sie im Grunde ein Versprechen über die Implementierungen Deref und DerefMut für diesen Typ.

set : Gegebener Pin

(und die Tatsache, dass wir nichts darüber wissen
Unpin), sollte dies nicht garantieren, dass sich P :: Target niemals bewegt? Wenn wir können
Neu initialisieren mit einem anderen P :: Target, sollte dies nicht unsicher sein?
Oder hängt das irgendwie mit Destruktoren und all dem zusammen?

Dadurch wird der alte Inhalt des Zeigers gelöscht und dort neuer Inhalt eingefügt. "Fixiert" bedeutet nicht "nie fallen gelassen", sondern "nie bewegt, bis fallen gelassen". Es ist also in Ordnung, es fallen zu lassen oder zu überschreiben, während Sie drop aufrufen . Das Anrufen von drop ist entscheidend, das ist die oben erwähnte Drop-Garantie.

Wo bewegt sich hier etwas?

map_unchecked : Frage :: Was ist hier das Gegenbeispiel? Wenn dies sicher wäre, was ist das Beispiel, das zeigt, dass die Garantien von Pin verletzt werden?

Ein Beispiel wäre, mit einem Pin<&&T> und mit dieser Methode ein Pin<&T> . Das Fixieren "propagiert" nicht durch Referenzen.

get_ref : Ein "vielleicht Gotcha" hier ist die innere Veränderlichkeit, was wäre, wenn T es wäre
RefCell?

In der Tat ist dies ein Gotcha, aber wie Sie festgestellt haben, kein Problem mit der Gesundheit. Was wäre unvernünftig ist , ein Verfahren, die sich von geht Pin<RefCell<T>> auf Pin<&[mut] T> . Grundsätzlich passiert, dass RefCell kein Pinning verbreitet, wir könnten impl<T> Unpin for RefCell<T> .

Wurde eine formelle Überprüfung (z. B. durch

Nein, nicht von unserer Seite. Die Blog-Beiträge, die ich oben erwähnt habe, enthalten einige Gedanken darüber, wie ich damit anfangen würde, dies zu formalisieren, aber wir haben es nicht wirklich durchlaufen und getan. Wenn Sie mir eine Zeitmaschine oder einen interessierten Doktoranden geben, machen wir das. ;)

Wenn wir den Anwendungsfall als Unterstützungsmechanismus für Async & Warten / Generatoren vergessen, könnten wir ihn dann in eine Kiste im Ökosystem stellen und er würde angesichts der aktuellen Garantien, die wir geben, nur funktionieren?

Das ist die Absicht.

Welche Art von APIs oder Typsystem-Ergänzungen werden durch die Stabilisierung der Pinning-API unmöglich? (Diese Frage ist eine Erweiterung von 2.)

Ich habe keine Ahnung, wie ich diese Frage mit Sicherheit beantworten soll. Der Raum möglicher Ergänzungen ist einfach zu groß und auch zu hochdimensional, als dass ich es wagen würde, irgendeine universelle Aussage darüber zu machen.

Welche Möglichkeiten bietet die zu stabilisierende API im Hinblick auf die Entwicklung einer Sprache und eines Pin-T-Typs zur Verbesserung der Feldprojektion und dergleichen (was mit der vorgeschlagenen API nicht besonders gut zu sein scheint).

Ich bin mir nicht mehr sicher über &pin T , es passt nicht sehr gut zu dem neuen generischen Pin<T> . In Bezug auf die Verarbeitung von Projektionen benötigen wir einen Hack, um zu sagen, dass " Unpin und Drop für diesen Typ nicht implementiert wurden", dann könnten wir dies sicher mit einem Makro tun. Für zusätzliche Ergonomie möchten wir wahrscheinlich eine generische "Feldprojektions" -Funktion in der Sprache, die auch den Übergang von &Cell<(A, B)> zu &Cell<A> abdeckt.

Gibt es irgendwo eine Erklärung dafür, warum! Unpin-Geräte konnten nicht stabilisiert werden?

AFAIK-Negativ-Impls haben einige langjährige Einschränkungen, und wenn Sie ihnen generische Grenzen hinzufügen, funktionieren sie oft nicht so, wie Sie es sich vorstellen. (Generische Grenzen werden manchmal einfach ignoriert oder so.)

Vielleicht behebt Chalk all das, vielleicht nur einen Teil davon, aber so oder so würden wir das wahrscheinlich nicht auf Chalk blockieren wollen.

Bedeutet gepackt, dass Felder dynamisch verschoben werden können? Das ist ein bisschen beängstigend.

Dies ist die einzige vernünftige Möglichkeit, drop in einem gepackten Feld aufzurufen, da drop eine ausgerichtete Referenz erwartet.

Sind wir sicher, dass llvm unter anderen Umständen niemals Code zum Verschieben von Feldern generiert? Ebenso ist es möglich, dass angeheftete Werte auf dem Stapel von llvm verschoben werden?

Das Kopieren von Bytes durch LLVM sollte kein Problem sein, da es das Programmverhalten nicht ändern kann. Hierbei handelt es sich um ein übergeordnetes Konzept des "Verschiebens" von Daten auf eine Weise, die in Rust beobachtet werden kann (z. B. weil Zeiger auf die Daten nicht mehr darauf verweisen). LLVM kann Daten nicht einfach an eine andere Stelle verschieben, auf die wir möglicherweise Zeiger haben. Ebenso kann LLVM nicht einfach Werte auf dem Stapel verschieben, deren Adresse übernommen wurde.

Ok danke @RalfJung , macht Sinn! Einige Anschlussfragen ...

Wenn Sie "nie bis zum Löschen verschoben" sagen, bedeutet dies, dass Drop aufgerufen werden kann, wenn &mut self von der Adresse Pin<&mut Self> verschoben wurde? Destruktoren können sich nicht darauf verlassen, dass die inneren Zeiger genau sind, oder?

Meine Sorge beim Betrachten von set war, wie mit Panik umgegangen werden würde, aber ich denke, dass dies kein Problem ist, nachdem ich mich ein bisschen mehr mit dem Codegen befasst habe.

Wenn Sie "nie bis zum Löschen verschoben" sagen, bedeutet dies, dass Drop aufgerufen werden kann, wenn &mut self von der Adresse Pin<&mut Self> verschoben wurde? Destruktoren können sich nicht darauf verlassen, dass die inneren Zeiger genau sind, oder?

@alexcrichton Mein Verständnis war, dass bedeutet, nie bewegt zu werden, bis Drop::drop zurückkehrt. Andernfalls werden einige Anwendungsfälle (zumindest aufdringliche Sammlungen und stapelzugewiesene DMA-Puffer) unmöglich.

Zerstörer können sich darauf verlassen, dass etwas nie bewegt wurde, wenn sie nachweisen können, dass es zuvor in einem Pin . Wenn eine Zustandsmaschine beispielsweise einen Status nur über eine API eingeben kann, für die ein Fixieren erforderlich ist, kann der Destruktor davon ausgehen, dass er vor dem Löschen fixiert wurde, wenn er sich in diesem Status befindet.

Code kann nicht davon ausgehen, dass nichtlokale Destruktoren keine Mitglieder verschieben, aber Sie können natürlich davon ausgehen, dass Ihre eigenen Destruktoren keine Dinge verschieben, weil Sie derjenige sind, der sie schreibt.

Wenn Sie "nie bis zum Löschen bewegt" sagen, bedeutet dies, dass "Tropfen" aufgerufen werden kann, wenn & mut self von der Adresse von Pin <& mut Self> verschoben wurde. Destruktoren können sich nicht darauf verlassen, dass die inneren Zeiger genau sind, oder?

Ich meine, dass sich die Daten niemals bewegen werden (in dem Sinne, dass sie nirgendwo anders hingehen, auch wenn sie nicht freigegeben werden), bis drop aufgerufen wurde. Und ja, drop kann sich darauf verlassen, dass es auf dem angehefteten Speicherort ausgeführt wird, obwohl sein Typ dies nicht ausdrücken kann. drop sollte Pin<&mut self> (für alle Typen) nehmen, aber leider zu spät dafür.

Nachdem drop aufgerufen wurde, sind die Daten nur bedeutungslose Bytes. Sie können alles damit machen: Legen Sie neue Dinge hinein, geben Sie die Zuordnung frei, was auch immer.

Dies ermöglicht beispielsweise eine aufdringliche verknüpfte Liste, bei der der Destruktor eines Elements die Registrierung durch Anpassen der benachbarten Zeiger aufhebt. Wir wissen, dass die Erinnerung nicht verschwinden wird, ohne dass dieser Destruktor aufgerufen wird. (Das Element kann immer noch durchgesickert sein , und dann bleibt es für immer in dieser verknüpften Liste. In diesem Fall bleibt es jedoch ein gültiger Speicher, sodass keine Sicherheitsprobleme auftreten.)

Ich habe alles gelesen, was ich auf Pin , Unpin und der Diskussion, die dazu geführt hat, finden kann, und während ich denke, ich verstehe jetzt, was los ist, gibt es auch viel von Subtilität in Bezug auf die Bedeutung der verschiedenen Typen sowie den Vertrag, den Implementierer und Benutzer einhalten müssen. Leider sehe ich relativ wenig Diskussion darüber in den Dokumenten für std::pin oder speziell für Pin und Unpin . Insbesondere würde ich gerne einige Inhalte aus diesen Kommentaren sehen:

in die docs aufgenommen. Speziell:

  • Dieses Pin schützt nur "eine Ebene tief".
  • Das Pin::new_unchecked schränkt das Gerät für Deref und DerefMut .
  • Die Interaktion zwischen Pin und Drop .
  • Wie !Unpin sich nur einmal bewegen darf, wenn er in einem Pin .
  • Die Begründung, warum Pin<Box<T>>: Unpin where T: !Unpin . Dies knüpft an die oben genannte Einschränkung "One Level Deep" an, aber ich denke, dieses konkrete Beispiel mit einer angemessenen Erklärung wäre für die Leser hilfreich.

Ich fand auch die Gegenbeispiele von @alexcrichton ziemlich hilfreich. Dem Leser eine Vorstellung davon zu geben, was bei den verschiedenen unsafe -Methoden über die reine Prosa hinaus schief gehen kann, würde meiner Meinung nach viel helfen (zumindest hat es mir sicherlich geholfen). Aufgrund der Subtilität dieser API möchte ich im Allgemeinen, dass die Sicherheitsabschnitte der verschiedenen unsicheren Methoden erweitert werden und möglicherweise auch aus den Dokumenten std::pin Modulebene

Ich habe diese API selbst noch nicht viel benutzt, daher vertraue ich dem technischen Urteil, dass sie jetzt in einem guten Zustand ist. Ich denke jedoch, dass die Namen Pin , Pinned und Unpin zu ähnlich und ausdruckslos sind für sehr unterschiedliche Typen / Merkmale und eine relativ komplexe API, was das Verständnis erschwert (wie aus einigen Kommentaren zu diesem Thread hervorgeht).

Sie scheinen den Namenskonventionen für Markereigenschaften zu folgen, daher kann ich mich nicht wirklich beschweren, aber ich frage mich, ob wir einen Kompromiss zwischen Ausführlichkeit und selbsterklärenden Namen eingehen könnten:

  • Pinned - etwas verwirrend, weil es das gleiche Wort wie Pin . Angesichts der Tatsache, dass es wie PhantomData , um eine Struktur mit Metainformationen zu erweitern, die sonst fehlen würden, wie wäre es mit PinnedData , PhantomPinned , PhantomSelfRef oder sogar DisableUnpin ? Etwas, das das Nutzungsmuster und / oder den Effekt anzeigt, den es haben wird.
  • Unpin - Wie in https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922 bin ich durch den Namen Unpin verwirrt, weil "un-pin "kann auf mehrere mehrdeutige Arten verstanden werden. So etwas wie IgnorePin , PinNeutral vielleicht?

Im Allgemeinen versage ich und finde selbst gute alternative Namen ...

PhantomPin und PinNeutral erscheinen mir als besonders nette Namen.

Um einen Kontrapunkt zu schaffen, habe ich Unpin intuitiv gefunden (sobald ich es verstanden habe). Pinned ist schwieriger zu halten, vorausgesetzt, ich habe es nicht in meinem eigenen Code verwendet. Was ist mit der Änderung von Pinned in NotUnpin ?

Ich bin damit einverstanden, dass das Finden besserer Namen eine lohnende Bikeshedding-Übung sein kann, um das Anheften zu erleichtern. Ich schlage vor:

  • Pin -> Pinned : Wenn Sie ein Pin , ist das wirklich ein Versprechen, dass das, was Sie erhalten, festgesteckt wurde und für immer festgesteckt bleibt. Ich fühle mich jedoch nicht zu stark dabei, da Sie auch davon sprechen könnten, einen "Pin of a Value" zu erhalten. Pinned<Box<T>> liest sich für mich einfach besser, vor allem, weil nur die Box angeheftet sind, nicht das Zeug, das sie enthalten.
  • Unpin -> Repin : Bei anderen Markereigenschaften sprechen wir normalerweise darüber, was Sie mit etwas tun können, das diese Markereigenschaft aufweist. Das ist vermutlich der Grund, warum Unpin gewählt wurde. Ich denke jedoch, dass der Leser hier wirklich mitnehmen soll, dass etwas, das Unpin ist, festgesteckt und dann ohne Konsequenz oder Überlegung an einer anderen Stelle wieder festgesteckt werden kann. Ich mag auch den Vorschlag von @ mark-im von PinNeutral , obwohl er etwas ausführlicher ist.
  • Pinned -> PermanentPin : Ich denke nicht, dass Pinned ein guter Name ist, weil etwas, das Pinned nicht wirklich fixiert ist ist einfach nicht Unpin . PhantomPin hat ein ähnliches Problem, da es sich auf Pin bezieht, wenn Pin nicht wirklich das ist, worauf Sie abzielen. NotUnpin hat ein doppeltes Negativ, was es schwierig macht, darüber nachzudenken. @Kimundis Vorschlag von PhantomSelfRef kommt ziemlich nahe, obwohl ich immer noch denke, dass es ein wenig "komplex" ist, und es bindet die Eigenschaft "kann nicht verschoben werden, wenn sie einmal fixiert ist" mit einer Instanz , in der dies der Fall ist (wenn Sie) eine Selbstreferenz haben). Mein Vorschlag könnte auch als PermanentlyPinned formuliert werden; Ich weiß nicht, welche Form weniger schlecht ist.

Ich denke, dass Pinned Ende NotX wobei X ist, was auch immer Unpin genannt wird. Die einzige Aufgabe von Pinned besteht darin, es so zu gestalten, dass der einschließende Typ Unpin nicht implementiert. (Bearbeiten: und wenn wir Unpin in etwas anderes ändern, ist das doppelte Negativ vermutlich kein Problem)

Repin macht für mich keinen Sinn, weil "Dieser Typ kann woanders angeheftet werden" nur ein Nebeneffekt von "Dieser Typ kann aus einem Stift herausgezogen werden" ist.

@tikue In gewissem Sinne geht es mir genauso, aber umgekehrt. Ich denke, Unpin sollte negativ formuliert werden "dies wird nicht durch Pin ", wohingegen Pinned positiv formuliert werden sollte "dies _ wird durch Pin ". Meist nur, um das doppelte Negativ zu vermeiden. Aber das ist ein Detail; Ich mag die Idee, dass es eine Dualität gibt. Vielleicht: s/Unpin/TemporaryPin , s/Pinned/PermanentPin ?

EDIT: Ja, ich sehe Ihren Standpunkt, dass Repin ein Nebeneffekt von Unpin . Ich wollte die Tatsache mitteilen, dass das Pin für einen Typ, der Unpin Pin ist, "unwichtig" ist, was ich nicht denke, dass Unpin super gut funktioniert. Daher der obige Vorschlag von TemporaryPin .

@jonhoo Ich denke, der Hauptgrund, warum ich das Gegenteil bevorzuge, ist, dass Pinned die Implementierung eines Merkmals verhindert, was für mich im einfachsten Sinne das wahre Negative ist.

Edit: was ist mit:

Unpin -> Escape
Pinned -> NoEscape

Interessant .. Ich versuche zu sehen, wie es in die Dokumentation passen würde. Etwas wie:

Wenn Sie ein Pin<P> , bedeutet dies im Allgemeinen, dass sich das Ziel von P nicht bewegt, bis es fallen gelassen wird. Die Ausnahme ist, wenn das Ziel von P Escape . Mit Escape gekennzeichnete Typen versprechen, dass sie auch dann gültig bleiben, wenn sie verschoben werden (z. B. enthalten sie keine internen Selbstreferenzen), und dürfen daher einem Pin "entkommen". Escape ist ein Auto-Merkmal, daher sind alle Typen, die vollständig aus Typen bestehen, die Escape sind, selbst auch Escape . Implementierer können dies jeden Abend mit impl !Escape for T {} oder durch Einfügen des Markers NoEscape von std::phantom deaktivieren.

Das scheint ziemlich anständig zu sein, obwohl die Verbindung zum Wort "Flucht" etwas schwach erscheint. Unabhängig davon wurde mir durch das Schreiben des obigen Artikels auch klar, dass Pin<P> nicht wirklich garantiert, dass das Ziel von P nicht verschoben wird (genau wegen Unpin ). Stattdessen wird garantiert, dass es egal ist, ob sich das Ziel von P bewegt oder ob sich das Ziel von P nicht bewegt. Ich weiß nicht, wie ich das verwenden soll, um eine bessere Auswahl an Namen zu treffen ... Aber es ist wahrscheinlich etwas, das es auf die eine oder andere Weise in die Dokumente schaffen sollte.

Persönlich habe auch ich eine große Abneigung gegen Unpin als Namen, vielleicht weil es am häufigsten als impl !Unpin das "not-un-pin" lautet und mehrere Gehirnzyklen erfordert (I. Ich bin ein älteres Modell.
Im Allgemeinen fällt es Menschen eher schwer, negativ als positiv zu denken (ohne direkte Quelle, aber im Zweifelsfall die Arbeit von Richard Hudson lesen).
Übrigens klingt Repin wirklich nett für mich.

Pin ist schwer zu erklären, da dadurch der angeheftete Wert nicht immer unbeweglich wird. Pinned ist verwirrend, weil es eigentlich nichts festnagelt. Es verhindert nur das Entkommen eines Pin .

Pin<P<T>> könnte als fixierter Wert erklärt werden: Fixierte Werte können nur verschoben werden, wenn der Wert ein Typ ist, der einem Pin entkommen kann.

Ein schnelles Googeln scheint zu zeigen, dass beim Wrestling, wenn man festgesteckt ist, das Ausbrechen einer Stecknadel als Flucht bezeichnet wird .

Ich mag auch den Begriff Flucht anstelle von Unpin aber ich würde mich für EscapePin .

Viele gute Gedanken hier!

Eine allgemeine Sache: Für mich als Nicht-Muttersprachler sind Pin und Unpin meistens Verben / Aktionen. Während Pin sinnvoll ist, da das Objekt jeweils an einem Speicherort fixiert ist, kann ich dasselbe für Unpin . Sobald ich eine Referenz von Pin<&mut T> erhalte, wird T immer in dem Sinne fixiert, dass sein Speicherort stabil ist, unabhängig davon, ob es sich um Unpin oder nicht. Es ist nicht möglich, ein Objekt wirklich als Aktion zu lösen. Der Unterschied besteht darin, dass für Unpin -Typen die Garantien des Fixierens nicht erforderlich sind, um bei weiteren Interaktionen eingehalten zu werden. Sie sind nicht selbstreferenziell und ihre Speicheradresse wird zB nicht an ein anderes Objekt gesendet und dort gespeichert.

Ich würde @tikue zustimmen, dass es schön wäre, eine Unpin tatsächlich bedeutet, aber es ist schwer zu quantifizieren. Es ist nicht nur so, dass diese Typen beweglich sind, und es ist auch nicht nur ihr Mangel an Selbstreferenzialität? Vielleicht geht es darum, dass "keine Zeiger im gesamten Speicherbereich ungültig werden, wenn das Objekt verschoben wird". Dann könnte etwas wie StableOnMoveAfterPin oder nur StableMove eine Option sein, aber das klingt auch nicht wirklich gut.

Repin für mich die gleichen Komplikationen wie Unpin , was bedeutet, dass eine Sache zuerst gelöst wird - was nach meinem Verständnis nicht der Fall ist.

Da das Merkmal meistens definiert, was passiert, wenn man ein Pin des Typs sieht, finde ich Dinge wie PinNeutral oder PinInvariant nicht schlecht.

In Bezug auf Pin vs Pinned würde ich Pinned bevorzugen, da dies der Status des Zeigers zu dem Zeitpunkt ist, zu dem man ihn sieht.

@ Matthias247 Ich glaube nicht, dass Ihnen garantiert ist, dass die Adresse von P::Target stabil ist, wenn P: Unpin ? Ich könnte mich jedoch irren?

@ Matthias247

Sobald ich eine Referenz von Pin <& mut T> erhalte, wird T immer in dem Sinne fixiert, dass sein Speicherort stabil ist, unabhängig davon, ob es nicht fixiert ist oder nicht.

Können Sie klarstellen, was Sie damit meinen? Bei einem T: Unpin Sie Folgendes schreiben:

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 Eigentlich eine gute Frage. Meine Argumentation war, dass die Futures verpackt werden und dann ihre poll() -Methode unter derselben Speicheradresse aufgerufen wird. Dies gilt natürlich nur für die Aufgabe / Zukunft der obersten Ebene, und die Zwischenebenen können Futures verschieben, wenn sie Unpin . Sie scheinen also Recht zu haben.

Für das neueste Bikeshedding:

  • Unpin : Was ist mit MoveFromPin ? Wenn mir noch einige Feinheiten fehlen, gibt dies meiner Meinung nach direkt an, welche Fähigkeit das Merkmal tatsächlich aktiviert: Wenn der Typ innerhalb eines Pin , können Sie ihn trotzdem verschieben.

    Entscheidend ist, dass es das Gleiche sagt wie Unpin , aber als positive Behauptung gerahmt. Anstelle des doppelt negativen !Unpin wir jetzt !MoveFromPin . Ich denke, ich finde das zumindest leichter zu interpretieren ... es sind Typen, die man nicht aus einer Stecknadel herausbewegen kann.

    (Es gibt Raum für Variationen der Grundidee: MoveOutOfPin , MoveFromPinned , MoveWhenPinned und so weiter.)

  • Pinned : Dies kann dann zu NoMoveFromPin , und es bewirkt, dass ein Typ !MoveFromPin . Ich denke, das scheint einfach genug zu sein.

  • Pin selbst: Dieser ist nicht mit den anderen beiden verbunden und auch nicht so bedeutend, aber ich denke, dass auch hier Raum für eine leichte Verbesserung besteht.

    Das Problem ist, dass Pin<&mut T> (zum Beispiel) nicht bedeutet, dass die &mut fixiert sind, sondern dass die T sind (eine Verwirrung, die ich gesehen habe mindestens ein neuer Kommentar). Da der Pin Teil als eine Art Modifikator für den &mut , gibt es einen Fall, in dem es besser wäre, ihn Pinning .

    Hierfür gibt es einen indirekten Präzedenzfall: Wenn wir die Überlaufsemantik eines Integer-Typs so ändern möchten, dass sie nicht in Panik gerät, sagen wir Wrapping<i32> statt Wrap<i32> .

All dies ist länger als die Originale, aber angesichts der heiklen Argumentation könnte dies eine lohnende Investition für mehr Klarheit sein.

Betreff: Repin , ich würde erwarten, dass dies so etwas wie ist

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

Dies könnte verwendet werden, um !Unpin -Typen in einer Vec -ähnlichen Sammlung zu unterstützen, die gelegentlich ihren Inhalt verschiebt (dies ist kein Vorschlag, ein solches Merkmal jetzt oder jemals hinzuzufügen, nur mein erster Eindruck aus dem Merkmalname).

Auch Bikshedding die Namen:

  • Pin<P> -> Pinned<P> : Der Wert, auf den der Zeiger P zeigt, wird für die gesamte Lebensdauer im Speicher gespeichert (bis er gelöscht wird).
  • Unpin -> Moveable : Der Wert muss nicht fixiert werden und kann frei verschoben werden.
  • Pinned (struct) -> Unmoveable : Das muss Pinned und kann nicht verschoben werden.

Ich denke nicht, dass sich Pin oder Unpin ändern sollte, die Alternativen fügen meiner Meinung nach alle Ausführlichkeit ohne Klarheit hinzu oder sind sogar ziemlich irreführend. Auch wir hatten dieses Gespräch bereits, haben eine Entscheidung getroffen, Pin und Unpin , und keines der in diesem Thread vorgebrachten Argumente ist neu.

Allerdings wurde Pinned seit der vorherigen Diskussion hinzugefügt, und ich denke, es ist sinnvoll, es PhantomPinned zu machen, um klar zu sein, dass es sich um einen Phantom-Markertyp wie PhantomData .

Persönlich habe auch ich eine große Abneigung gegen Unpin als Namen, vielleicht weil es am häufigsten als impl! Unpin angesehen wird, das "not-un-pin" liest und mehrere Gehirnzyklen erfordert (ich bin ein älteres Modell), um zu schließen dass es bedeutet "okay, dieses wird für immer gepinnt, sobald es das erste Mal gepinnt wird", also kann ich nicht einmal das doppelte Negativ wegoptimieren.

Dies ist genau das Gegenteil meiner Erfahrung. Die manuelle Implementierung von !Unpin bedeutet, dass Sie eine selbstreferenzielle Struktur von Hand mithilfe von unsicherem Code implementieren, einem extrem nischen Anwendungsfall. Im Gegensatz dazu hat alles, was eine potenziell unpin-Struktur hinter einem Zeiger hält, eine positive Implikation von Unpin . Alle Impls von Unpin in std haben zum Beispiel eine positive Polarität.

@withoutboats Könnten Sie einen Link zur vorherigen Diskussion über diese Namen bereitstellen, in der die hier vorgebrachten Argumente bereits durchgesetzt wurden?

Hier ist ein Thread, der sicherlich auch zum Thema RFC-Thread und Tracking https://internals.rust-lang.org/t/naming-pin-anchor-move/6864 besprochen wurde

(Die Typen in diesem Thread, die als Anker und Stift bezeichnet werden, heißen jetzt Pin<Box<T>> und Pin<&'a mut T> )

Soll Unpin als Abkürzung für Unpinnable gelesen werden? Unpinnbar wie in Sie können den Wert nicht wirklich fixiert halten, obwohl er sich in einem Pin befindet. (Ist das überhaupt ein korrektes Verständnis von meiner Seite?)

Ich habe einige der Dokumente und Kommentarthreads durchgesehen und keinen speziellen Verweis auf Unpinnable gefunden.

Unpin soll für nichts zu kurz sein. Eine Sache, die meiner Meinung nach für viele Benutzer nicht offensichtlich ist, aber wahr ist, ist, dass der Standard-Styleguide für Bibliotheken darin besteht, Verben als Merkmalsnamen und nicht als Adjektive zu bevorzugen - daher Send , nicht Sendable . Dies wurde nicht mit perfekter Konsistenz angewendet, aber es ist die Norm. Unpin ist wie in "Unpin", da es möglich ist, diesen Typ von dem Pin zu lösen,

Namen wie Move (nicht "beweglich", denken Sie daran) sind weniger klar als Unpin weil sie implizieren, dass es damit zu tun hat, dass sie überhaupt verschoben werden können, anstatt das Verhalten mit dem zu verbinden Stifttyp. Sie können !Unpin Typen verschieben, da Sie einen beliebigen Sized Wert in Rust verschieben können.

Namen, die ganze Phrasen sind, wie ich gesehen habe, wären für std sehr unidiomatisch.

Es mag nicht kurz sein, aber genau so habe ich es gelesen. Da Merkmale Verben sind, müssen Sie sie manuell in ein Adjektiv umwandeln, wenn Sie damit einen Typ beschreiben möchten, anstatt eine Operation für einen Typ. std::iter::Iterator wird durch etwas implementiert, das iterierbar ist, std::io::Seek wird durch etwas implementiert, das durchsuchbar ist, std::pin::Unpin wird durch etwas implementiert, das nicht feststellbar ist.

@withoutboats irgendein spezifisches Problem mit einigen der anderen Namen, die das Escape oder EscapePin ? Ich habe verstanden, dass diese Diskussion schon einmal stattgefunden hat, aber vermutlich sind jetzt viel mehr Augen darauf gerichtet, daher bin ich mir nicht sicher, ob es sich um eine völlig überflüssige Wiederholung handelt ...

Eine Sache, die ich für wahr halte, ist, dass es bedauerlich ist, dass Pin und Unpin als Paar gelesen werden können (einige Typen sind "pin" und andere "unpin"), wenn Pin soll ein Substantiv sein , kein Verb . Die Tatsache, dass Pin kein Merkmal ist, klärt hoffentlich die Dinge. Das Argument für Pinning ist sinnvoll, aber hier stoßen wir auf das Problem der Namenslänge. Insbesondere da Methodenempfänger self zweimal wiederholen müssen, haben wir am Ende viele Zeichen: self: Pinning<&mut Self> . Nicht davon überzeugt, dass Pinning<P> ganze vier Zeichen der Klarheit über Pin<P> .

@tikue Die "Escape"

Auch wir hatten dieses Gespräch bereits, haben eine Entscheidung getroffen, Pin und Unpin zu verwenden, und keines der in diesem Thread vorgebrachten Argumente ist neu.

Das reibt mich in die falsche Richtung - ist der Erfahrungsbericht der Community unerwünscht? Ich persönlich habe kein klares Problem mit Unpin bis einige der anderen Kommentare in diesem Thread sowie das Nebeneinander mit Pinned doppelt negativ waren.

Rust führt keine Fluchtanalyse durch, daher bin ich mir nicht sicher, ob ich das als echtes Problem betrachte.

: bell: Dies tritt nun in die endgültige Kommentierungsphase ein , wie oben beschrieben . :Glocke:

Ich würde immer noch gerne Verbesserungen der Dokumentation sehen, wie in https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 beschrieben, bevor dies landet :)

Ich habe https://github.com/rust-lang/rust/pull/55992 geöffnet, um die oben vorgeschlagene Dokumentation hinzuzufügen und Pinned in PhantomPinned umzubenennen.

Ich denke, Pinned (und PhantomPinned ) ermutigt dazu, einen "angehefteten" Wert als einen Wert zu konzipieren, der nicht aus einem Pin , was bedeutet, dass viele Werte in einem Pin (diejenigen, deren Typen Unpin implizieren) sind nicht "fixiert"!

Das scheint verwirrend. Ich finde es einfacher, alle Werte in einem Pin als fixiert zu konzipieren, während sie im Pin , und ob das Pinning dauerhaft ist oder nicht, ist das, was früher Pinned Kontrollen. Ein von Pin* getrennter Name würde die Verschmelzung zweier unterschiedlicher Konzepte verhindern.

PhantomNotUnpin : P.

Persönlich habe auch ich eine große Abneigung gegen Unpin als Namen, vielleicht weil es am häufigsten als impl! Unpin angesehen wird, das "not-un-pin" lautet und mehrere Gehirnzyklen erfordert

Vielen Dank! Ich habe mich auch schon seit einiger Zeit mit Unpin ich war nicht in der Lage, genau zu bestimmen, warum. Jetzt denke ich zu verstehen: Es ist die doppelte Verneinung.

Dies ist genau das Gegenteil meiner Erfahrung mit der manuellen Implementierung! Unpin bedeutet, dass Sie eine selbstreferenzielle Struktur von Hand mit unsicherem Code implementieren, einem extrem nischen Anwendungsfall. Im Gegensatz dazu hat alles, was eine potenziell unpin-Struktur hinter einem Zeiger hält, eine positive Implikation von Unpin. Alle Impls von Unpin in std haben zum Beispiel eine positive Polarität.

Es geht aber nicht nur um Implementierung, sondern auch um Diskussion. impl !Sync ist ziemlich selten (nicht allein, weil es instabil ist), aber über Sync und !Sync Typen zu sprechen ist ziemlich häufig. In ähnlicher Weise tauchten !Unpin in Diskussionen über diese Funktion ziemlich häufig auf, zumindest die, die ich hatte.

Auch ich würde etwas bevorzugen, das eine Eigenschaft positiv ausdrückt ( MoveFromPin oder so). Ich bin von der Ergonomie nicht ganz überzeugt, da man im Gegensatz zu Pin dieses Merkmal nicht so oft gebunden schreiben muss.

Rust führt keine Fluchtanalyse durch, daher bin ich mir nicht sicher, ob ich das als echtes Problem betrachte.

LLVM tut dies, daher ist die Fluchtanalyse für Rust immer noch sehr relevant.

Der Weg, dies zu lösen, besteht darin, Wörter auszuwählen, die die Bedeutung von Pin / Unpin . Benennen Sie beispielsweise Unpin in Relocate . Dann wird aus !Unpin !Relocate . Das macht für mich viel intuitiver Sinn - ich habe es als "Oh, Objekte dieses Typs können nicht verschoben werden" gelesen. Ein weiterer Anwärter ist Movable .

Ich bin mir nicht sicher, was das entgegengesetzte Wort ist, das Pin ersetzen könnte, oder ob wir es überhaupt brauchen. Aber ich könnte mir durchaus vorstellen, dass die Dokumente so etwas sagen:

Ein angeheftetes Objekt kann genau dann direkt über DerefMut geändert werden, wenn das Objekt im Speicher verschoben werden kann. Relocate ist eine automatische Eigenschaft - sie wird standardmäßig hinzugefügt. Wenn es jedoch direkte Zeiger auf den Speicher gibt, der Ihre Werte enthält, deaktivieren Sie Relocate indem Sie Ihrem Typ impl !Relocate hinzufügen.

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

Dies ist für mich weitaus intuitiver als Unpin .

Ich habe # 55992 geöffnet, um die oben vorgeschlagene Dokumentation hinzuzufügen

Dies fügt jedoch nur einen Teil dessen hinzu, was in https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891 vorgeschlagen wurde.

Ich mag den Vorschlag MoveFromPin . Relocate ist auch gut, aber vielleicht nicht genug mit Pin . Man könnte es wieder als unbeweglichen Typ verstehen (was es nicht ist). RelocateFromPin wäre wieder gut.

Escape ing ist eine Sache, die zB auch in Swift mit Schließungen verbunden ist und ob sie innerhalb oder außerhalb der aktuellen Anrufkette aufgerufen werden. Das klingt irreführend.

Ich sehe keine Probleme mit längeren Namen, solange dies zur Klarheit beiträgt.

FWIW Ich würde gerne auch eine Stimme abgeben, um Unpin in etwas "Positives" wie Relocate oder MoveFromPin (oder noch ausführlicher, aber vielleicht etwas genauer MayMoveFromPin umzubenennen

Ich bin damit einverstanden, dass das doppelte Negativ von !Unpin oder nur Unpin mich in der Vergangenheit verwirrt hat, und etwas Positives, "das sich bewegen kann, obwohl es sich in Pin ", denke ich würde helfen, einige Verwirrung zu lindern!

FWIW Ich dachte anfangs dasselbe über Unpin , aber als ich es tatsächlich IMO benutzte, machte es Sinn - es ist nicht wirklich eine doppelte Negation, da die Operation, die Sie suchen, die Fähigkeit ist, Unpin (nimm etwas frei in Pin und aus) nicht die Fähigkeit, Dinge in einer Stecknadel zu halten. Es ist dasselbe wie MoveFromPin , nur anders formuliert. Ich würde einen Namen bevorzugen, der die Leute nicht denken lässt, dass er "nicht Pin " oder so ist, aber IMO MoveFromPin und andere sind viel zu wortreich. UndoPin ? ( FreePin für die Haskell-Menge?)

Ich denke immer noch, dass !Unpin komisch liest - ich habe meine innere Stimme so trainiert, dass sie eher wie "Mach das nicht auf!" Behandelt wird. anstelle des üblichen "Implementiert nicht Unpin ", aber es ist einige Mühe erforderlich.

Was ist mit !Pluck ?

@ Runiq

anstelle des üblichen "Unpin wird nicht implementiert"

Ich habe dies in meinem früheren Kommentar erwähnt, aber ich denke, dies ist eine gute Möglichkeit, es auszudrücken: Unpin ist die Operation zum Ein- und Aussteigen aus Pin<C<_>> . Dinge, die Unpin nicht implementieren, bieten diese Fähigkeit nicht.

@cramertj Ich mag UndoPin

@cramertj Ich stimme zu, dass ich kein großer Fan von Alternativen bin, die bisher vorgeschlagen wurden, aber ich persönlich würde das wortreiche MoveFromPin gegenüber Unpin bevorzugen. Es ist ein guter Punkt, dass es kein Doppel-Negativ ist, aber beim Lesen (als jemand, der noch keine Tonne damit gearbeitet hat) stolpert es mich immer wieder, dass es ein Doppel-Negativ ist. Ich versuche immer wieder, das Präfix "Un" als negativ zu lesen ...

Ich bin neugierig, aber @cramertj oder andere haben Sie das Gefühl, dass es einen guten Unpin hochkommt? Ist es super selten? Ist es super üblich? Häufig genug, um ein Schmerz zu sein, wenn es ein Schmerz zum Tippen ist?

Für einen Kurz und süße Namen mich persönlich wie die Relocate Idee, aber für länger-and-wortreicher-but-ok-weil-du-nicht-Typ-it-as-much I wie MoveFromPin . Ich persönlich bin der Meinung, dass unabhängig von der Ergonomie der Eingabe des Namens beide besser sind als Unpin

Ich bin neugierig, aber @cramertj oder andere haben Sie das Gefühl, dass es einen guten Griff gibt, wie viel die Unpin-Bindung ergonomisch gebunden ist? Ist es super selten? Ist es super üblich? Häufig genug, um ein Schmerz zu sein, wenn es ein Schmerz zum Tippen ist?

Nach meiner Erfahrung gibt es zwei Fälle, in denen Unpin tatsächlich im Benutzercode erscheint:

  1. Unpin Grenzen werden angezeigt, weil Sie beim Abrufen tatsächlich eine Zukunft verschieben müssen (z. B. Dinge wie einige ausgewählte APIs).
  2. Wenn Ihre Zukunft nur über etwas anderes als eine Zukunft generisch ist, möchten Sie normalerweise eine bedingungslose Implementierung von Unpin hinzufügen, da es keine Rolle spielt, ob diese anderen Typen Unpin da Sie dies nie getan haben Pin-Projekt zu ihnen.

Ein Beispiel für den zweiten Fall ist, wenn Sie einen Puffertyp haben, über den Sie generisch sind (z. B. T: AsRef<[u8]> ). Sie müssen es nicht anheften, um das Slice herauszuholen. Es ist Ihnen also egal, ob es Unpin implementiert oder nicht. Sie möchten also nur sagen, dass Ihr Typ Unpin bedingungslos implementiert, damit Sie es implementieren können Future Pinning ignorieren.

Unpin wird häufig als Grenze angesehen - select! , StreamExt::next und andere Kombinatoren erfordern alle, dass die Typen, mit denen sie arbeiten, Unpin .

@withoutboats Ich bin neugierig auf Ihren Punkt 2 dort; Denken wir, dass impl Unpin etwas ist, an das sich die Leute erinnern müssen, um es oft zu implementieren? Ähnlich wie Bibliotheksautoren heutzutage oft vergessen, #[derive(Debug)] oder impl std::error::Error für ihre benutzerdefinierten Typen zu verwenden, was die Verwendung dieser Bibliotheken erschwert?

Unpin ist ein Auto-Merkmal. Das einzige Mal, wenn Sie einen Typ haben, der Unpin nicht implementiert, ist, wenn dieser Typ explizit davon abweicht. (Oder enthält ein Feld, das nicht mehr angehängt ist).

Ich verstehe, dass das der Fall ist. Und ich nahm an, dass dies bei weitem der häufigste Fall sein würde, weshalb ich überrascht war, dass @withoutboats sogar den zweiten Punkt erwähnte. Es deutet darauf hin, dass es häufiger vorkommt als ursprünglich gedacht (allerdings vielleicht nur bei der Implementierung Ihrer eigenen Zukunft), daher bin ich gespannt auf die Häufigkeit solcher Anwendungsfälle :)

@alexcrichton Ich bin neugierig, aber @cramertj oder andere haben Sie das Gefühl, dass es einen guten Griff gibt, wie viel ergonomisch der Unpin gebunden ist? Ist es super selten? Ist es super üblich? Häufig genug, um ein Schmerz zu sein, wenn es ein Schmerz zum Tippen ist?

Ich hatte einen langen Beitrag über meine Erfahrungen mit der Portierung meines Codes auf Futures 0.3 geschrieben (das Pin ). Sie müssen es nicht lesen, die Zusammenfassung lautet:

Die meiste Zeit brauchen Sie sich überhaupt keine Sorgen um Unpin zu machen, da Unpin für fast alle Typen automatisch implementiert wird.

Das einzige Mal, dass Sie sich um Unpin sorgen müssen, ist:

  1. Sie haben einen Typ, der gegenüber anderen Typen generisch ist (z. B. struct Foo<A> ).

  2. Und Sie möchten eine Pinning-API (z. B. Future / Stream / Signal ) für diesen Typ implementieren.

In diesem Fall müssen Sie Folgendes verwenden:

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

Oder dieses:

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

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

Dies ist normalerweise die einzige Situation, in der Unpin benötigt wird. Wie Sie sehen können, bedeutet dies normalerweise, dass Sie Unpin ~ 2 Mal pro Typ verwenden müssen.

Bei Nicht-Kombinatoren oder bei Typen, die Future / Stream / Signal nicht implementieren, müssen Sie Unpin überhaupt.

Ich würde also sagen, dass Unpin sehr selten auftaucht und nur in der Situation, dass Future / Stream / Signal Kombinatoren erstellt werden.

Ich bin also stark für einen Namen wie MoveFromPin . Das Fixieren ist eine Nischenfunktion, mit der sich die meisten Menschen nie befassen müssen. Daher sollten wir die Namenslänge nicht zu stark optimieren.

Ich denke, die kognitiven Vorteile (Vermeidung von Doppelverneinung) sind viel wichtiger als das Speichern einiger Zeichen in seltenen Situationen.

Vor allem, weil das Feststecken schon schwer zu verstehen ist! Machen wir es uns also nicht unnötig schwerer.

@jonhoo denken wir, dass impl Unpin etwas ist, an das sich die Leute erinnern müssen, um es oft zu implementieren? Ähnlich wie Bibliotheksautoren heutzutage oft vergessen, #[derive(Debug)] oder impl std::error::Error für ihre benutzerdefinierten Typen zu verwenden, was die Verwendung dieser Bibliotheken erschwert?

Ich glaube nicht, dass es möglich ist, impl Unpin zu vergessen, denn wenn der Autor es vergisst, wird ein Compilerfehler angezeigt, der verhindert, dass seine Kiste überhaupt veröffentlicht wird. Es ist also überhaupt nicht wie #[derive(Debug)] .

Die Situation, von der @withoutboats spricht, ist nur für Leute Future / Stream / Signal Kombinatoren implementieren. Sie betrifft niemanden (insbesondere nicht) nachgeschaltete Benutzer der Bibliothek).

(Ich denke, doppelte Verneinung ist ein Teil davon, und vielleicht ist es auch einfach mehr Verneinung als unbedingt notwendig, aber ich denke, es ist nicht die ganze Geschichte (hier wird versucht, nach innen zu schauen) ... "Unpin" ist ein bisschen , metaphorisch? Indirekt? Es macht Sinn, wenn es erklärt wird, und da "Feststecken" selbst bereits eine Metapher ist, ist nicht klar, warum mein Gehirn ein Problem damit haben würde, diese Metapher weiterzuentwickeln, aber mein Gehirn findet dennoch die Bedeutung von aus irgendeinem Grund "unpin" und schwer festzuhalten.)

Ich denke du hast Recht @cramertj , es ist eigentlich keine doppelte Negation im üblichen Sinne, aber wie @alexcrichton und @glaebhoerl werde ich immer wieder davon gestolpert. „Un-“ als Präfix hat ein sehr Negation-y Gefühl zu ihm ( „unsicher“, „nicht umgesetzt“ und so weiter, wie ich in die Regel , dass der Präfix auftreten), und es wird negiert hier Pinning, wenn auch nur als Verb.

@withoutboats Ich bin neugierig auf Ihren Punkt 2 dort; Denken wir, dass impl Unpin etwas ist, an das sich die Leute erinnern müssen, um es oft zu implementieren? Ähnlich wie Bibliotheksautoren heutzutage häufig vergessen, # [abzuleiten (Debug)] oder impl std :: error :: Error für ihre benutzerdefinierten Typen zu implizieren, was die Verwendung dieser Bibliotheken erschwert?

Absolut nicht! Wenn ein Benutzer manuell ein Future implementiert, das über eine Nicht-Zukunft generisch ist, möchte er wahrscheinlich in der Lage sein, den Status in seiner zukünftigen Implementierung zu ändern, ohne unsicheren Code zu schreiben - das heißt, Pin<&mut Self> zu behandeln &mut self . Sie erhalten Fehler, die darauf hinweisen, dass MyFuture<T: AsRef<[u8]>> Unpin nicht implementiert. Die beste Lösung für dieses Problem ist dann die Implementierung von Unpin . Dies hat jedoch nur Auswirkungen auf den Benutzer, der versucht, Future zu implementieren, und es ist unmöglich, ihn zu vergessen, da sein Code nicht kompiliert werden kann.

Die Situation, von der @withoutboats spricht, ist nur für Personen

Ich spreche speziell von generischen manuellen Futures ohne Kombinator , die nur pauschale Implikationen von Unpin selbst wenn ihre Generika nicht Unpin .

Ich habe ein Flussdiagramm für die Frage "Was mache ich beim Fixieren, wenn ich eine manuelle Zukunft / einen manuellen Stream implementiere?" Erstellt.

pinning-flowchart

Um ein bisschen mehr Fahrrad zu fahren, habe ich mir heute beim Mittagessen LeavePin ausgedacht. Es hat den gleichen Ton wie escape ohne die implizite irreführende Semantik.

Gibt es interessante Wechselwirkungen zwischen Impl-Spezialisierung und Pins, wie es mit der Lebensdauer der

Die Teilzeichenfolge "specializ" tritt bei der Diskussion von Tracking-Problemen ein wenig auf, ist jedoch nicht schlüssig. Pin-RFC oder Spezialisierungs-RFC sollten diese Interaktion ausdrücklich erwähnen, entweder als "Es wurde überprüft, ob sie in Ordnung ist" oder "Weitere Untersuchungen sind erforderlich, um sie als sicher zu beurteilen".

@vi gibt es nicht nur keine Soliditätsinteraktion, es ist auch nicht möglich, dass es eine Soliditätsinteraktion gibt. Die Pin-APIs sind in der Standardbibliothek streng definiert und enthalten keine neuen Sprachfunktionen. Ein Benutzer kann sie genauso einfach in Bibliotheken von Drittanbietern definieren. Wenn eine Sprachfunktion angesichts des vorhandenen Bibliothekscodes nicht korrekt ist, ist dies ein fehlerhafter Zeitraum , da jeder Benutzer diesen Bibliothekscode heute schreiben kann und er problemlos kompiliert werden kann.

Wenn eine Sprachfunktion angesichts des vorhandenen Bibliothekscodes nicht korrekt ist, ist dies ein fehlerhafter Zeitraum, da jeder Benutzer diesen Bibliothekscode heute schreiben kann und er problemlos kompiliert werden kann.

Nicht, dass es irgendeinen Einfluss auf pin ... aber ich denke nicht, dass es fast so einfach ist.

Wenn eine Bibliotheksfunktion bei Verwendung von unsafe Sprachkonstrukte verwendet, deren Verhalten noch nicht spezifiziert ist (z. B. &packed.field as *const _ oder verschiedene Annahmen über ABI), machen zusätzliche Sprachänderungen die Annahmen ungültig Von diesen Bibliotheken denke ich dann, dass es die Bibliotheken sind, die nicht gesund sind und nicht die Sprachänderungen. Wenn andererseits Sprachänderungen das definierte Verhalten unsound machen, liegt es an den Sprachänderungen. Feines Kompilieren ist daher keine ausreichende Voraussetzung für die Solidität einer Bibliothek angesichts unsicherer und sprachlicher Änderungen.

+1 auf MoveFromPin oder ähnliches

Wenn Sie die Frage "Wann sollte ich Unpin für meinen eigenen Typ entfernen?" Stellen, ist die Antwort viel klarer, wenn Sie stattdessen fragen: "Wann sollte ich MoveFromPin für meinen eigenen Typ nicht implementieren?"

Gleiches gilt für "Soll ich Unpin als hier gebundenes Merkmal hinzufügen?" vs "Soll ich MoveFromPin als hier gebundenes Merkmal hinzufügen?"

Pinning ist nicht! Bewegen

Es tut mir leid, wenn dies irgendwo erwähnt wurde, aber ich habe nur die große Menge an Diskussionen überflogen, die hier um Pin, im Implementierungsproblem und im RFC-Problem geführt wurden.

Wird Rost jemals haben! Bewegen? Ich kann sicherlich Anwendungsfälle für so etwas sehen (zum Teufel, ich habe mich nach Pin umgesehen, weil ich nach einer Möglichkeit gesucht habe, mich nicht mit Typen in den Fuß zu schießen, die nicht bewegt werden können). Wenn die Antwort ja ist, wie würde das mit Pin interagieren? Würde die Existenz von Pin es schwieriger machen, als es bereits hinzuzufügen ist?

Die letzte Kommentierungsfrist mit einer Disposition zum Zusammenführen gemäß der obigen Überprüfung ist nun abgeschlossen .

Es sollte eine ungelöste Sorge um die Benennung von Unpin geben.

Wie @RalfJung darauf hingewiesen , 55992 # fügt auch nur eine kleine Menge der zusätzlichen Dokumentation gebeten in https://github.com/rust-lang/rust/issues/55766#issuecomment -438.316.891 und anderswo. Ich weiß nicht, dass dies ein Grund dafür ist, noch nicht zu verschmelzen.

Warum nimmt meine drop () -Methode jetzt wieder & mut self anstelle eines Pins?

Nun, drop () ist alt - es existiert seit Rust 1.0 - also können wir es nicht ändern. Wir würden es lieben, Pin <& mut Self> zu nehmen, und dann könnten Unpin-Typen ihre & mut bekommen, wie sie es jetzt tun, aber das ist eine nicht abwärtskompatible Änderung.

Ich habe mich gefragt, ob es möglich ist, diese Änderung abwärtskompatibel umzusetzen. AIUI bis wir Unpin hinzufügen (und die Leute !Unpin angeben können), implementieren alle Typen Unpin . Also könnten wir ein Merkmal hinzufügen:

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

und dann impliziere dieses Merkmal für alle Unpin -Typen, die, bis die Leute sich abmelden können, alle Typen sind. Etwas wie:

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

Dann müsste der Compiler Aufrufe an DropPinned::drop anstelle von Drop::drop einfügen. Im Wesentlichen wird das Merkmal DropPinned zum Lang-Item und nicht zum Drop . AFAICS Dies wäre nur dann abwärtskompatibel, wenn dieser Mechanismus gleichzeitig mit Unpin .

Es sollte eine ungelöste Sorge um die Benennung von Unpin geben.

@tikue Kein libs-Teammitglied hat vor oder während des FCP Bedenken bei rfcbot eingereicht, und ich glaube nicht, dass die Argumente zu Unpin neu oder neu in diesem Thread waren. Daher würde unser Prozess normalerweise den aktuellen berücksichtigen Benennung abgeschlossen werden. Wenn jemand Bedenken hat, sollte er sich natürlich melden, aber der Sinn von Team-Kontrollkästchen, denen FCP folgt, besteht darin, sicherzustellen, dass jeder die API wie vorgeschlagen stabilisieren kann.

@cramertj Ich bin ein wenig verwirrt. Mehrere Leute haben gesprochen. Als ich nach Referenzen fragte, wo sonst die Argumente zur Benennung von Unpin vorgebracht und gelöst worden wären, wurde ich auf https://internals.rust-lang.org/t/naming-pin-anchor- verwiesen. sich meines Erachtens auch Leute über die Benennung von Unpin beschweren und kein echtes Gegenargument. Der ursprüngliche RFC in https://github.com/rust-lang/rfcs/pull/2349 hat auch nicht viele Gründe, warum vorgeschlagene Alternativen zu Unpin schlechter sind. Selbst in diesem Thread scheinen die einzigen Gegenargumente, die wirklich auftauchen, "es ist kürzer" und "es ist technisch korrekt" zu sein. Können Sie auf eine konkrete Diskussion verweisen, in der alternative Namen, die leichter zu verstehen sind (z. B. MoveFromPin ), diskutiert und abgelehnt werden?

Ich habe in früheren Kommentaren erklärt, warum ich glaube, dass in diesem Thread neue Perspektiven angesprochen wurden. Ich habe die Pin-API-Diskussionen von Anfang an ziemlich genau verfolgt und kann mich nicht erinnern, jemals das doppelt negative Problem gesehen zu haben, das vor diesem Thread angesprochen wurde.

@tikue Ich habe das doppelt negative Problem mehrfach Unpin wurde mehrfach als Problem angesprochen und konsequent zugunsten von Unpin gelöst nicht die Benennung von Unpin als ungelöste Frage: Wir haben die Alternativen diskutiert, und dieser FCP war der Prozess, um zu entscheiden, dass wir bereit waren, die getroffenen Entscheidungen, einschließlich des Namens Unpin zu stabilisieren.

@cramertj Könnten Sie bitte einen Link Unpin , weil ich nicht glaube, dass sie hier gegeben wurden. Wie bereits erwähnt, bieten die Referenzen, die ich bisher erhalten habe, keine Lösung für die Benennung von Unpin .

@cramertj +1 auf @jonhoos Frage. Wenn es Diskussionen gibt, die das libs-Team untereinander geführt hat und die nicht in offiziellen Kanälen registriert sind, sollte der Hauptschwerpunkt dieser Diskussionen hier wiederholt werden. Ich denke, es gibt sogar eine offizielle Regel, dass RFC-Entscheidungen nur auf der Grundlage öffentlich bekannter Argumente getroffen werden können.

Ich denke, es gibt sogar eine offizielle Regel, dass RFC-Entscheidungen nur auf der Grundlage öffentlich bekannter Argumente getroffen werden können.

Ja, das stimmt - und fürs Protokoll, ich bin nicht im libs-Team und war daher nicht nur für Diskussionen im libs-Team anwesend. Beim Durchsuchen des RFC-Threads und des Tracking-Problems Pin der Name Unpin mehrfach erwähnt. Ich sehe niemanden, der speziell die Worte "doppelt negativ" sagt, aber ich erinnere mich sicher, dass " !Unpin ist ein doppelt negatives", das zuvor erwähnt wurde, zusätzlich zu der allgemeinen API-Regel, Merkmale für das zu benennen, was Sie können mache mit ihnen, anstatt mit dem, was du nicht kannst (wie ich oben ausgeführt habe, denke ich, dass Unpin tatsächlich diesen beiden Regeln folgt, obwohl zu erkennen ist, dass man "Unpin" als Verb hören muss, anstatt es als Adjektiv zu hören "not-pin", was für Leute nicht intuitiv ist).

@wmanley Das funktioniert nicht, zB würde impl<T> Drop for Vec<T> kaputt gehen, weil wir nicht Vec<T>: Unpin . Auch in diesem Thread wurden bereits Vorschläge in dieser Richtung gemacht. Bitte lesen Sie die Diskussionen, bevor Sie antworten. Ich weiß, dass das ziemlich viel verlangt, aber es ist die einzige Möglichkeit, zu vermeiden, dass dieselben Probleme immer wieder erklärt werden.

Ich denke, es gibt sogar eine offizielle Regel, dass RFC-Entscheidungen nur auf der Grundlage öffentlich bekannter Argumente getroffen werden können.

Dies wird informell als "keine neue Begründung" -Regel bezeichnet .

Sie sind sich nicht sicher, wo Sie dies veröffentlichen sollen, aber könnte jemand einen Blick auf https://github.com/rust-lang/rust/issues/56256 werfen?

Im Zusammenhang mit # 56256 wird impl<T> From<Box<T>> for Pin<Box<T>> im OP nicht als stabilisiert aufgeführt, wird jedoch implizit stabil, sobald Pin tut. Gibt es andere Merkmalsimplementierungen, die nicht trivial sind und zur Stabilisierung in Betracht gezogen werden sollten? (Das Scannen der Dokumente aller anderen scheint eine triviale Delegierung von Implementierungen für den umschlossenen Zeiger an mich zu sein.)

Wir haben heute im libs-Team über dieses Problem gesprochen. Die letzten Diskussionswochen haben gezeigt, dass noch einige Dinge zuerst angesprochen werden sollten, insbesondere in Bezug auf die Benennung von Unpin .

Daher werden wir dies vorerst nicht stabilisieren (trotz FCP).

Es wäre sehr dankbar, wenn jemand die Ideen in diesem Thread zusammenfassen und einen eigenständigen Vorschlag zur Verbesserung der Benennungssituation vorbereiten könnte.

@ Kimundi

Es wäre sehr dankbar, wenn jemand die Ideen in diesem Thread zusammenfassen und einen eigenständigen Vorschlag zur Verbesserung der Benennungssituation vorbereiten könnte.

Bedeutet dies, dass das libs-Team nicht bereit ist, die aktuellen APIs unverändert zu stabilisieren? Ich persönlich glaube nicht, dass irgendjemand einen besseren Namen als die derzeit implementierten Namen gefunden hat, und daher kann ich keinen solchen Vorschlag machen, aber es ist mir sehr wichtig, dass diese APIs stabilisiert werden. Wenn also jemand aus dem libs-Team einen Namen hat, den er bevorzugt, dann: shipit:

Bikeshedded dies mit einer Gruppe von Kollegen und @anp schlug DePin , was ich eigentlich ziemlich mag, da es die "nicht pin" Konnotation von Unpin und betont, dass es sich um einen Typ handelt das kann de Pin 'd sein.

@Kimundi Kannst du oder jemand aus dem libs-Team bitte ein explizites "fcp-Anliegen" registrieren, um dies aus dem FCP herauszunehmen und klarer darzulegen, was unter "ein paar Dinge, die angesprochen werden sollten" zu verstehen ist?

@rfcbot betrifft die Benennung von Unpin

Ich bin mir nicht sicher, ob dies wirklich funktioniert, wenn FCP eingegeben wurde, aber ich möchte die Benennung des Merkmals Unpin formell blockieren. Das Merkmal Unpin scheint für die API ziemlich wichtig zu sein, und das "doppelte Negativ" (wie oben beschrieben) wirft mich jedes Mal, wenn ich es lese, auf eine Spritztour.

Es gab eine ganze Reihe von Kommentaren zu verschiedenen Namen, aber leider bin ich noch nicht sonderlich begeistert von irgendwelchen. Mein "Favorit" ist immer noch im Sinne von MoveFromPin oder Relocate (mein Gott, hier gibt es so viele Kommentare, dass ich keine Ahnung habe, wie ich das überprüfen soll).

Ich persönlich bin mit der Benennung von Pin selbst sowie Pinned für eine ZST, die das Merkmal Unpin nicht implementiert, einverstanden.

Ich stimme @alexcrichton voll und ganz zu, dass die Benennung von Unpin hier der große Streitpunkt ist. Ich glaube nicht, dass es technische Bedenken gibt, soweit ich das vorgeschlagene Feature selbst sehen kann (obwohl es viele Kommentare gibt, könnte also etwas übersehen worden sein).

Ich denke immer noch, dass Pinned ein seltsamer Name für die ZST ist, weil etwas, das Pinned nicht wirklich fixiert ist. Es ist einfach nicht Unpin . PhantomPinned (wie es in # 55992 umbenannt wurde) hat das gleiche Problem, da es sich auf Pin bezieht, wenn die ZST _really_ ungefähr Unpin .

Ich denke auch immer noch, dass die Dokumente angesichts der Subtilität dieser Funktion mehr Arbeit benötigen, aber das zählt wahrscheinlich nicht als Blocker.

Außerdem, @Kimundi , bin ich froh zu sehen, dass das libs-Team bereit ist, diesem etwas mehr Zeit zu geben, um sich niederzulassen. Es mag viel wie unnötiges Bikeshedding erscheinen, aber ich (und auch andere) denke, dass es ziemlich wichtig ist, die Lehrbarkeit dieser Funktion zu verbessern :)

Ich bin mit dass Pinned immer noch komisch anfühlt und PhantomPinned nicht besser ist (es ist in keiner Weise fixiert, nicht einmal in einer Phantom-Art). Ich denke, wenn wir einen guten Namen für Unpin , dann Pinned natürlich für die Umbenennung in Not{NewNameForUnpin} .

Ich glaube wirklich nicht, dass wir mehr Zeit damit verbringen müssen, über PhantomPinned diskutieren - dieser Typ wird Endbenutzern fast nie erscheinen. PhantomNotUnpin / PhantomNotDePin / PhantomNotMoveFromPin / etc. werden es nicht mehr oder weniger häufig machen oder die API für Benutzer, die sich bereits wohl genug fühlen, mehr oder weniger offensichtlich machen mit der API, um eine legitime Verwendung für PhantomPinned .

Nur eine kurze Idee: Merkmal Move und ZST Anchor .

Jeder Typ ist Move fähig, es sei denn, er enthält Anchor , wodurch er an einem Pin .
Mir gefällt, wie intuitiv Anchor: !Move sehr viel Sinn macht.

Ich schlage nicht vor, dass wir Zeit speziell mit PhantomPinned verbringen, aber ich denke, es wäre ein geringer Aufwand, offen zu bleiben, da es möglich ist, dass alles funktioniert, worauf für Unpin gelandet ist genauso gut für PhantomPinned .

Wir haben Move und mehrfach erklärt, warum dies unangemessen ist. Alle Typen sind beweglich, bis sie fixiert sind. In ähnlicher Weise wurde zuvor Anchor vorgeschlagen, aber aus dem Namen geht nicht hervor, dass es verwendet wird, um Unpin ing / Move ing zu deaktivieren.

@stjepang Ich denke, Move wurde vor einiger Zeit verworfen, weil etwas, das !Unpin ist, nicht wirklich verhindert, dass es verschoben wird. Nur wenn ein Typ unter einem Pin liegt und es nicht Unpin ist, sind Sie "vertraglich verpflichtet", ihn nicht zu verschieben.

Im ursprünglichen Kommentar sagte @withoutboats :

Der Wrapper Pin ändert den Zeiger so, dass der Speicher, auf den er verweist, an Ort und Stelle "angeheftet" wird

Ich finde es seltsam, dass Typen Unpin implementieren, weil Werte nicht "fixiert" sind - Speicher ist. Es ist jedoch sinnvoll, über Werte zu sprechen, die "sicher zu bewegen" sind. Was ist, wenn wir Unpin in MoveSafe umbenennen?

Betrachten Sie das Merkmal UnwindSafe . Es ist nur ein "Hinweis" -Marker und ist sicher zu implementieren. Wenn Sie einen !UnwindSafe -Wert eine catch_unwind -Grenze überschreiten lassen (mit AssertUnwindSafe ), "brechen" Sie ihn, indem Sie seine Invarianten ungültig machen.

Wenn Sie einen !Unpin / !MoveSafe haben, kann dieser (natürlich) weiterhin verschoben werden, aber Sie "brechen" ihn, indem Sie seine Selbstreferenzen ungültig machen. Das Konzept scheint ähnlich.

Das Merkmal Unpin bedeutet wirklich nur MoveSafe . Mir scheint, es geht nicht um Werte, die hinter Pin aus dem Speicher verschoben werden können. Es geht vielmehr um Werte, die beim Verschieben nicht "brechen".

MoveSafe hat das gleiche Problem wie Move - alle Werte eines beliebigen Typs können sicher verschoben werden. Es wird erst unmöglich, einen Wert zu verschieben, nachdem er fixiert wurde.

MoveSafe hat das gleiche Problem wie Move - alle Werte eines beliebigen Typs können sicher verschoben werden.

Richtig, aber es ist eine andere Bedeutung von "sicher", genau wie in UnwindSafe . Wie auch immer, ich würde gut mit Relocate oder so etwas umgehen können.

Zusammenfassend bin ich der Meinung, dass der Name des Merkmals nicht DePin , Unpin oder irgendetwas mit "Pin" im Namen sein sollte. Für mich ist das die Hauptursache für Verwirrung. Das Merkmal ist nicht wirklich eine "Notluke" aus den Fesseln von Pin - das Merkmal besagt, dass der Wert beim Verschieben nicht ungültig wird.

Ich sehe nur Pin und Unpin als völlig getrennte Dinge. :) :)

Ich fühle genau das Gegenteil;). Das Merkmal ist nur in Bezug auf Pin von Bedeutung. Dies ist der einzige Typ, der Einschränkungen in Bezug auf die Beweglichkeit des zugrunde liegenden Werts aussagekräftig ausdrückt. Ohne Pin ist Unpin völlig bedeutungslos.

Ich denke gerne darüber nach, etwas fest auf eine Pinnwand zu stecken. Wenn Sie den Stift eines Objekts entfernen, das an der Platine befestigt ist, können Sie das Objekt verschieben. Das Entfernen des Stifts löst sich.
Ich mag den Namen Unpin .

Ich kann auch sehen, wie! Unpin ist eine doppelte Verneinung und kann Verwirrung stiften. Ich frage mich jedoch, wie oft Sie !Unpin schreiben müssen.

Ein anderer Name, den ich mir für Unpin einfallen lassen könnte, ist Detach . Zurück zur Pinnwand-Metapher würden Sie nicht Pin entfernen, sondern Pin von seinem Objekt lösen.

Ich denke, ich mag DePin wirklich! Bisher ist es mein Favorit - es ist prägnant, es ist eindeutig ein Verb und kein Adjektiv, und !DePin scheint auch ziemlich klar zu sein ("kann nicht entfernt werden").

Ich finde es seltsam, dass Typen Unpin implementieren, weil Werte nicht "fixiert" sind - Speicher ist.

Der Wert wird im Speicher fixiert. Aber auch hier ist der Wert von entscheidender Bedeutung. Beim Feststecken des Speichers geht es für mich nur darum, sicherzustellen, dass es oder so dereferencierbar bleibt, aber es wird nicht von mem::swap verletzt. Beim Fixieren eines Werts in den Speicher geht es darum, diesen fixierten Wert nicht an eine andere Stelle zu verschieben. Genau darum geht es bei Pin .

Ich kann auch sehen, wie! Unpin ist eine doppelte Verneinung und kann Verwirrung stiften. Ich frage mich jedoch, wie oft Sie schreiben müssen!

Ich höre dich, wenn du sagst, dass du den Namen nicht verwirrend findest. Ich und viele andere in diesem Thread waren durch die Benennung verwirrt.

Ich habe den Code nicht zur Hand, aber als ich zum ersten Mal versuchte, Futures zu verwenden, wollte ich, dass eine Funktion ein impl Future<Output=T> . Ich kann mich nicht erinnern, was passiert ist, aber ich habe sofort einen knorrigen Compilerfehler bekommen, der sich über T und Unpin beschwert hat. Die Frage, auf die ich eine Antwort brauchte, war "Ist es sicher, T darauf zu beschränken, nur Unpin zu sein". Und das brachte mich dazu, ungefähr zwei Stunden lang in den Abgrund zu starren.

"Oh, ok, wenn das Pin bedeutet. Also bedeutet Unpin ... Box? Warum ist das eine Eigenschaft?"

"Warte, impl !Unpin ? Warum ist das nicht impl Pin ?"

"Richtig, Pin und Unpin ... sind keine Gegensätze. Sie sind völlig verschiedene Dinge. Warten Sie, was bedeutet Unpin dann wieder? Warum heißt es so?"

"Was um alles in der Welt bedeutet !Unpin ? Nicht ... das andere, nicht Pin-Ding? Hrrrgh"

Der einzige Weg, wie dies in meinem Kopf Sinn macht, besteht darin, "unpin" durch "relocatable" zu ersetzen. "Typ kann nicht in den Speicher verschoben werden" ist absolut sinnvoll. Und selbst wenn ich

Das Umbenennen von Unpin in Relocatable (oder Relocate ) erhält meine Stimme 🗳. Aber ich würde fast jeden der anderen Vorschläge besser finden als Unpin.

Das Merkmal ist nur in Bezug auf Pin von Bedeutung. Dies ist der einzige Typ, der Einschränkungen in Bezug auf die Beweglichkeit des zugrunde liegenden Werts aussagekräftig ausdrückt. Ohne Pin ist Unpin völlig bedeutungslos.

Um es auf den Punkt zu bringen: Die Garantien für Pin beziehen sich ausschließlich auf bestimmte Verhaltensweisen , nicht auf Typen . Zum Beispiel kann eine Generatorfunktion , die ergibt () könnte triviale implementieren FnOnce wiederholt wieder aufzunehmen. Während dieser Typ möglicherweise Unpin nicht implementiert - da seine Ertragszustände selbstreferenziell sein könnten, ist seine FnOnce -Schnittstelle (die sich selbst bewegt) völlig sicher, da sie bei FnOnce noch nicht fixiert wurde Unpin sich speziell auf die Arten von Verhaltensweisen, die als sicher gelten, sobald der Typ fixiert wurde (nämlich das Verschieben), und nicht auf eine intrinsische Eigenschaft des Typs.

Ironischerweise kam ich hier nur zu kommentieren , dass , während die Unpin Namensgebung auf jeden Fall in der Vergangenheit diskutiert wurde, die Debatte darüber Ich erinnere mich Zeugnis war , als wir entschieden uns ersetzen Move mit Unpin , was, wie ich sagen wollte, eine eindeutige Verbesserung darstellt. Und oft merkt man erst später, dass das aktuelle Design zwar eine deutliche Verbesserung gegenüber dem Vorgänger darstellt, aber noch Raum für weitere Verbesserungen bietet. Aus welcher Richtung kam ich dazu?

Bei Unpin geht es speziell um die Arten von Verhaltensweisen, die nach dem Fixieren des Typs als sicher gelten (dh um das Verschieben), und nicht um eine intrinsische Eigenschaft des Typs.

Das angeheftete Verhalten ist eine intrinsische Eigenschaft des Typs, genau wie das Verhalten / die Invariante, wenn es gemeinsam genutzt wird. Ich habe in diesem Blog-Beitrag ausführlicher

@RalfJung wir sprechen aneinander vorbei. Es gibt eine Verwirrung, die ich oft sehe, dass selbstreferenzielle Typen "nicht verschoben werden können" - dies ist nicht korrekt, sie haben bestimmte Zustände, in die sie eintreten können, in denen sie nicht verschoben werden können, aber während sie sich in anderen Zuständen befinden, ist dies vollkommen sicher um sie zu verschieben (und unsere APIs basieren auf der Fähigkeit, sie zu verschieben, z. B. um Kombinatoren auf sie anzuwenden oder sie in ein Pin<Box<>> ). Ich versuche zu klären, dass es nicht der Fall ist, dass diese Typen "nicht verschoben werden können".

Ah ja, dann stimme ich zu. Ein !DePin -Typ ist nicht immer fixiert , und wenn dies nicht der Fall ist, kann er wie jeder andere Typ verschoben werden.

@cramertj @withoutboats eine Sache, die ich noch nicht ganz sicher war, sind Sie gegen die Umbenennung von Unpin ? Es hört sich nicht so an, als ob Sie der Meinung sind, dass es umbenannt werden muss, aber ich bin mir nicht sicher, ob Sie dagegen sind.

Ich persönlich glaube nicht, dass das Hauptproblem hier im Namen Unpin und dass "wenn wir es einfach umbenennen könnten, wäre alles intuitiv". Während das Umbenennen ein wenig hilfreich sein kann (und Relocate / DePin hier nett zu sein scheint ...), denke ich, dass die Hauptkomplexität vom Konzept des Fixierens selbst herrührt. Es ist sicherlich nicht eines der am einfachsten zu verstehenden oder zu erklärenden Konzepte. nicht einmal annähernd.

Daher denke ich, dass die Dokumentation des core::pin -Moduls _bedeutend_ verbessert werden muss und viel mehr Beispiele enthalten sein müssen. Gute Beispiele würden sowohl kanonische Anwendungsfälle als auch Verwendungen der unsicheren Funktionen und Implementierungen zeigen, die nicht einwandfrei sind.

@alexcrichton Ich bin nicht gegen das Umbenennen, nein. Ich denke, Unpin ist bereits in Ordnung, aber ich wäre mit DePin , weshalb ich es vorgeschlagen habe. RemoveFromPin ist am offensichtlichsten "richtig", aber bis zum syntaktischen Salz ausführlich, daher denke ich, dass ich diesen Namen speziell ablehnen würde. Ich bin jedoch dagegen, die Stabilisierung auf unbestimmte Zeit zu verschieben, bis wir einen Namen finden, dem alle zustimmen, dass er der beste ist. Ich denke, die API weist einige inhärente Komplexitäten auf, die aufgrund des Namens Unpin nicht dramatisch verbessert oder verschlechtert werden können futures_api weiter abwandern (und wir können damit beginnen, die Teile in Position zu bringen, um futures_api zu stabilisieren selbst). Vielleicht sollten wir ein spezielles VC-Meeting zur Namensfindung planen, damit jeder, der eine Meinung hat, seine Lösung vorschlagen kann und wir eine Gelegenheit mit höherer Bandbreite haben, dies zu regeln?

Meine Stimme ist std::pin::Reloc (Umzug)

Ich habe zwei sehr hypothetische Situationen (nun ja, wirklich nur eine, aber zwei verschiedene Ausführungen), von denen ich gerne explizit angegeben hätte, ob dies erlaubt ist oder nicht.

Unpin gibt an, dass es trivial ist, von allen Zuständen von T (wobei T: Unpin ) im angehefteten Typstatus zu wechseln (ich hoffe, ich erinnere mich richtig an diesen Begriff von einem der Blog-Beiträge von @RalfJung ) bis zum nicht Pin &mut T .

Nehmen wir also an, ich möchte einen Typ Woof erstellen, für den es auch immer möglich ist, von allen Zuständen von Woof im angehefteten Typstatus in den nicht angehefteten Typ zu wechseln. Zustand, aber es ist nicht trivial, dies zu tun und kann daher Unpin nicht implementieren. Woof erlaubt, eine Funktion wie fn unpin(self: Pin<Box<Woof>>) -> Box<Woof> ?

Gleiches gilt für einen Typ Meow für den es nur manchmal möglich ist, von fixiert zu nicht fixiert zu wechseln, und für eine Funktion wie fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>> .

Meine 2 cts für das Bikeshedding:

Wenn ich das richtig verstehe, bedeutet Unpin wirklich " Pin hat keine Auswirkungen auf mich".

Was ist mit so etwas wie BypassPin oder IgnorePin ?

Nehmen wir also an, ich möchte einen Woof-Typ erstellen, für den es auch immer möglich ist, von allen Woof-Zuständen im angehefteten Typstatus in den nicht angehefteten Typstatus zu wechseln, aber dies ist nicht trivial kann daher Unpin nicht implementieren, wäre es Woof erlaubt, eine Funktion wie fn unpin (self: Pin) zu haben>) -> Box?

Ja, das sollte möglich sein. Wenn beispielsweise Woof ein Element einer aufdringlichen verknüpften Liste ist, kann es eine Funktion zum Entfernen des Elements aus der Liste und zum gleichzeitigen Entfernen von Pin bereitstellen (wobei dies für nicht argumentiert wird) -anqueued Woof s, die beiden Typestates sind äquivalent, so dass wir sie entkoppeln können).

Relocate hat für mich das gleiche Problem wie RePin Dies könnte eine Operation bedeuten, die es ermöglicht, einen Wert von einem angehefteten Ort an einen anderen zu verschieben, ohne den Wert vollständig zu entfernen. Es ist eine weniger starke Konnotation als RePin , scheint aber immer noch etwas verwirrend.

Dies könnte eine Operation implizieren, die es ermöglicht, einen Wert von einer angehefteten Stelle an eine andere zu verschieben, ohne den Wert vollständig zu entfernen.

Das ist zwar nicht genau das, worum es bei diesem Merkmal geht, aber es ist auch nicht ganz falsch: In den meisten Anwendungsfällen ist das Verschieben eines Werts von einem fixierten Ort an einen anderen genauso katastrophal wie die freie Verwendung. Angesichts der Tatsache, dass jeder ein Pin<Box<T>> erstellen kann, verstehe ich nicht einmal, warum Sie hier eine grundlegende Unterscheidung treffen.

In den meisten Anwendungsfällen ist das Verschieben eines Werts von einem angehefteten Ort an einen anderen genauso katastrophal wie das freie Verwenden.

Ja, weshalb es nützlich sein kann, ein Merkmal zu haben, das diese Operation einem Typ hinzufügt (dies kann kein Markierungsmerkmal wie Unpin da möglicherweise Dinge wie das Aktualisieren interner Referenzen erforderlich sind). Ich schlage nicht vor, dass wir jetzt so etwas hinzufügen, nur dass es etwas ist, das ich in Zukunft sehen könnte (entweder in std oder bei Dritten), was zu einer verwirrenden Überlappung von Namen führen könnte.

Ich kenne derzeit keine starken Anwendungsfälle dafür, aber ich habe darüber nachgedacht, so etwas mit angehefteten Sammlungen zu verwenden.

Ok, hier sind einige Überlegungen. Ich habe mich anfangs gefragt, ob wir einen Namen wie PinInert oder PinNoGuarantees haben könnten, da dies auch die Beschreibung ist, aber darüber nachzudenken, was ich wirklich will, ist eine Eigenschaft, die eine Aktion im Gegensatz zu einer beschreibt Eigentum, oder zumindest macht es viel einfacher, in meinem Kopf darüber nachzudenken.

Ein Problem mit Unpin (ich bin mir nicht sicher warum) ist, dass ich nicht durch den Kopf zu bekommen scheint, dass die beabsichtigte Bedeutung "das Entfernen von einem Stift, das Lösen ist, sicher ist ". Das Merkmal wie es ist vermittelt die Handlung "das Auflösen", aber ich kann das nicht ganz verstehen, wenn ich es lese. Es fühlt sich komisch an, Pin<T: Unpin> denn wenn es "unpin" ist, warum ist es in einem Pin?

Ich frage mich, ob ein Name wie CanUnpin funktionieren könnte. Eine Menge davon geht es nicht um eine harte Garantie eine oder andere Weise (implementierende Unpin bedeutet nicht , dass Sie es von einem Stift zu entfernen, es bedeutet nur , dass Sie es von einem Stift entfernen). Wie klingt das? Ist CanUnpin für andere lesbar genug? Kurz genug?

(Das Präfix Can vermittelt mir das Verb auch viel einfacher und es besagt, dass Sie es von einer Stecknadel entfernen können , aber Sie werden es nicht immer tun).


Als eine andere nicht verwandte Tangente habe ich vergessen, früher darauf hinzuweisen (Entschuldigung!), Dass ich nicht denke, dass alle oben genannten Methoden notwendigerweise inhärente Methoden sein sollten. Wir hatten bereits eine Menge Fehler in Bezug auf Inferenz- und Kollisionsmethoden, und das Hinzufügen sehr gebräuchlicher Namen wie as_ref und as_mut für Typen, die auch Deref implementieren, scheint ein Problem zu sein darauf warten, passiert zu werden.

Kommen diese Operationen häufig genug vor, um den riskanten Ort zu rechtfertigen, an dem sie platziert werden sollen? (von Natur aus) Oder werden sie selten genug verwendet, um den sicheren Weg der damit verbundenen Funktionen zu bewältigen?

Da ich anscheinend jetzt Skin in diesem Spiel habe: CanUnpin scheint mir im Geiste sehr nahe zu Unpinnable oder ähnlichem zu sein, und mein Eindruck war immer, dass die Community diese Art von Modifikatoren missbilligt Merkmalsnamen, da die meisten Merkmale die Aktion beschreiben, die der Typ ausführen kann. Das Impl selbst ist in diesem Sinne eine implizite "Dose" oder "-fähig". Was auch immer entschieden wird (und der Name spielt in dem hier angegebenen Umfang keine Rolle, IM-nicht-so-HO - wahrscheinlich müssen nur sehr wenige Benutzer dies eingeben), ich möchte alle dazu ermutigen, die Fragen hier dringend zu lösen. Ich freue mich auf diese Stabilisierung und weiß, dass es viele, viele Menschen sind!

@alexcrichton

Ein Problem mit Unpin (ich bin mir nicht sicher warum) ist, dass ich nicht durch den Kopf zu bekommen scheint, dass die beabsichtigte Bedeutung "das Entfernen von einem Stift, das Lösen ist, sicher ist ".

Was ist, wenn Sie T: Unpin als " T ist immun gegen Pin " betrachten? Wenn Sie dann Pin<T: Unpin> haben, bedeutet dies nur, dass wir einen Wert innerhalb von Pin der immun gegen Pinning ist, sodass Pinning hier effektiv umstritten ist.

Oder mit anderen Worten: Unpin neutralisiert Pin . Um ehrlich zu sein, habe ich das nach so vielen Diskussionen irgendwie verinnerlicht und jetzt macht es Sinn. 😆

Bei vielen davon geht es nicht um eine harte Garantie auf die eine oder andere Weise (die Implementierung von Unpin bedeutet nicht, dass Sie es von einem Pin entfernen, sondern nur, dass Sie es von einem Pin entfernen können).

Ein Kontrapunkt dazu ist das Merkmal Send . Es bedeutet nicht , dass Sie den Wert senden, es bedeutet nur , Sie senden können.

@anp Ich stimme zu, dass CanUnpin kein großartiger Name ist. Ich versuche mein Bestes, um einen besseren Namen zu finden! Es scheint, dass nur wenige der Meinung sind, dass dies umbenannt werden muss, da alle Vorschläge aus den gleichen Gründen abgeschossen zu werden scheinen, aus denen ich denke, dass Unpin muss.

Zur Erinnerung: Jede Stabilisierung fährt die Züge wie alle anderen Änderungen, und mit einer Veröffentlichung nächste Woche wird dies definitiv nicht in diese Veröffentlichung gelangen und wird als nächstes ein Kandidat für die nächste Veröffentlichung sein. Das heißt, wir haben 7 Wochen, fast zwei Monate Zeit, um all dies zu landen, um sicherzustellen, dass es so schnell wie möglich eingeht. Obwohl ich der Meinung bin, dass Dringlichkeit notwendig ist, ist es nur "Lasst uns diese Dringlichkeit nicht vergessen", nicht "Lasst uns diese Dringlichkeit vor Montag lösen".

@stjepang Auch ich habe mich jetzt an Unpin gewöhnt , Pin apis weiß, um diese Dokumentation anschließend zu lesen und zu überprüfen, ob sie zum Lernen ausreicht.

Ich möchte auch die Stabilisierung einer verbesserten Dokumentation formell blockieren, da dies für dieses Problem besonders wichtig ist.

@rfcbot betrifft die Verbesserung der Dokumentation

Konkret ist meiner Meinung nach eine bessere Dokumentation erforderlich:

  • Mehr Prosa zu jeder Methode darüber, warum sie sicher und / oder warum sie unsicher ist. Zum Beispiel ist der Vertrag über die Implementierung von P DerefMut "vernünftiges Verhalten" nicht auf Pin::new_unchecked dokumentiert.
  • Jede unsafe -Funktion sollte ein Codebeispiel dafür enthalten, warum sie unsicher ist, oder zumindest eine klare Erklärung einer Abfolge von Schritten, die schief gehen können.
  • Ich bin der Meinung, dass die Moduldokumente detaillierter beschrieben werden können, als nur, was ein Pin ist und was es bedeutet. Ich denke, sie würden von Informationen wie "wie dies keine allgemein unbeweglichen Typen sind, sondern eine Form davon" oder "wie wird Pin in der Praxis verwendet, zum Beispiel bei Futures" profitieren.
  • Ich würde gerne ein Beispiel mit Generika und Unpin in der Dokumentation oder in Codebeispielen sehen. Zum Beispiel klingt es so, als müssten viele Kombinatoren in Futures damit umgehen, und ähnlich erfordern einige Kombinatoren dies speziell. Einige Anwendungsbeispiele, wie man mit Generika arbeitet und wie sie sich abspielen, könnten helfen, Unpin ziemlich gut zu verstehen.

Ich bin auch neugierig, ob andere konkrete umsetzbare Elemente haben, die sie ebenfalls zur Dokumentation hinzufügen können!

Als nächstes möchte ich auch die self -Frage mit as_ref formell blockieren, aber ich vermute, dass dies schnell gelöst werden kann. (Es tut mir wieder leid, dass ich nicht daran gedacht habe, dies früher anzusprechen.)

@rfcbot betreffen Selbstmethoden

Dies schlägt as_ref , as_mut , get_ref , get_mut , into_ref und set um alle Methoden auf dem zu sein Pin Typ. In dem Vorschlag wird erwähnt, dass wir dies aufgrund von Methodenkonflikten nicht für intelligente Zeiger tun. Die Erfahrung zeigt jedoch, dass die heute implementierte Pin API nicht konsistent ist und häufig nur im Weg steht.

Diese Methodennamen sind jedoch sehr kurze und süße Namen und kommen im gesamten Ökosystem häufig vor. Das libs-Team ist in der Vergangenheit auf eine unendliche Reihe von Fehlern gestoßen, bei denen wir Trait-Implementierungen und dergleichen hinzufügen, und es kommt zu Konflikten mit vorhandenen Trait-Methoden in verschiedenen Kisten. Diese Methoden scheinen aufgrund ihrer Namen ein besonders hohes Risiko zu sein. Außerdem ist unklar, ob wir in Zukunft jemals mehr Methoden zu Pin hinzufügen könnten, da selbst wenn die Namen jetzt einen zukünftigen Namen ergeben, ein zusätzliches Kollisionsrisiko besteht.

Ich möchte nur sichergehen, dass wir diese Abweichung von der Konvention überdenken. Ich persönlich bin besonders besorgt über die Konsequenzen und ich denke, es wäre großartig, wenn wir einen Mittelweg finden könnten, der diese Gefahren vermeidet.

Und während ich dabei bin, ist mir noch eine letzte Sache aufgefallen, und ich denke wieder, dass dies ziemlich schnell gelöst werden kann

@rfcbot betrifft Box-Pinnned-vs-Box-Pin

Wurde der Name Box::pin für Box::pinned berücksichtigt? Mit der Existenz von Pinned und / oder PhantomPinned scheint es ordentlich zu sein, wenn der Name mit dem Rückgabetyp übereinstimmen könnte!

@alexcrichton Hat Sie etwas Besonderes von MoveFromPin als Option überzeugt? Ich sehe, Sie waren früher dafür günstig (und mehrere Leute schienen es auch zu mögen). Die direkten Einwände, an die ich mich erinnern oder die ich durch Strg + F-ing schnell finden konnte, sind: "Namen, die ganze Phrasen sind ... wären sehr unidiomatisch" (@withoutboats) und "viel zu wortreich" ( @cramertj).

Ich muss zugeben, dass Motivationen im Sinne von "Ich habe mich daran gewöhnt ... jetzt, nachdem ich so viel darüber nachgedacht habe" mich ziemlich unruhig machen. Der Mensch kann sich an alles gewöhnen und es post-hoc rationalisieren. Egal wie der Name lautet, es wäre eine große Überraschung, wenn wir uns nicht irgendwann daran gewöhnen würden. (Dies ist genau der Grund, warum wir offensichtlich suboptimale Namen wie existential type in anderen Fällen als temporäre Namen wählen, um zu verhindern, dass sie dauerhaft werden.)

Das versehentliche Privilegieren der Perspektiven erfahrener Benutzer (die wir alle sind!) Gegenüber anderen, die Dinge mit frischerem Verstand angehen, ist der Fehler, den wir meiner Meinung nach immer wachsam machen müssen, so hart es auch ist .

Es ist sicherlich nicht der Stil der Standardbibliothek, aber ich habe auch das Gefühl, dass CanUnpin irgendwie klarer macht, wie dieses bestimmte Stück mit den anderen zusammenpasst.

Es ist unmöglich, einen Namen zu erfinden, der einem Anfänger die Bedeutung von Unpin , ohne die Dokumentation zu lesen. Send und Sync sind für Anfänger ebenfalls schwer zu verstehen, aber sie sehen gut und prägnant aus, ebenso wie Unpin . Wenn Sie nicht wissen, was diese Namen bedeuten, müssen Sie ihre Dokumente lesen.

@valff der Tat das ist richtig, aber es gibt nicht wenige Menschen, die die Dokumente gelesen und verstanden haben , wie Werke Pinning, aber die Unpin name noch bewirkt , dass sie erhebliche psychische Schwierigkeiten, während die anderen Namen weniger verursachen.

@glaebhoerl Oh, tut mir leid, um MoveFromPin oder CanUnpin bin als von den aktuellen Unpin . Ich versuche nur, zu einer Schlussfolgerung zu gelangen!

Ich habe versucht, den aktuellen Vorschlag zu verstehen. Vielleicht kann diese Art von Fortschrittsbericht auch ein zusätzliches Licht auf die Benennung werfen.

  • Wenn Sie die Garantien von Pin auf eine stabile Adresse ausweiten, bis Drop zusätzliche Anforderungen an den Ersteller eines Pin . Ich hätte es nützlich gefunden, wenn die notwendigen Voraussetzungen für den Aufruf von unsafe fn new_unchecked(pointer: P) -> Pin<P> direkt erwähnt worden wären. Zu verstehen, wie ein Pin erstellt werden kann, hilft enorm beim Verständnis ihres Konzepts, imo.
  • Die in der Tracking-Ausgabe erwähnte Zusammenfassung mit dem Präfix we agreed that we are ready to stabilize them with no major API changes. enthält viele alte APIs. Gleichzeitig enthält es auch viele Einblicke in Drop und die relevante Pin-Projektion. Es enthält auch eine explizite Formulierung der zusätzlichen Garantie, auf die in Punkt 1 Bezug genommen wird und die in diesem Bericht nicht enthalten ist. Diese Mischung aus veralteten und frischen Informationen war etwas verwirrend. Ziehen Sie in Betracht, alle Informationen in dieses Problem zu verschieben oder veraltete Absätze anzugeben.
  • Da die zusätzliche Garantie als slight extension , habe ich erwartet, dass es eine Möglichkeit gibt, sie abzulehnen. Da das Fixieren einmal einen Typstatus auf diesem Zeiger enthält, bis er gelöscht wird, können Sie auf irgendeine Weise von diesem Typstatus in einen "normalen" Status zurückkehren. Tatsächlich dachte ich zuerst, dass Unpin zu tun ist, aber ich denke, dass Unpin tatsächlich etwas stärker ist. Es heißt, dass der fixierte Zustand nach Belieben frei in einen nicht fixierten Zustand umgewandelt werden kann. Es ist in Ordnung, dass die aktuelle Schnittstelle in dieser Hinsicht minimal ist, aber Unpin könnte mit einer Operation für den Typ verwechselt werden, der eine innere Invariante entfernt, die den Pin-Status erfordert (wie eine weiche Version von Drop ). .

Klarstellung: Ich persönlich bevorzuge MoveFromPin gegenüber den anderen Namen, aber es ist wohl künstlich und klobig.

Neben den bereits von @alexcrichton angeforderten wichtig , die Feldregel für die Pin-Projektion in map_unchecked und map_unchecked_mut zu erläutern. Etwas in der Art von:

Ein Pin verspricht, die referenzierten Daten erst zu verschieben, wenn sie gelöscht werden. Da die Felder einer Struktur nach ihrer enthaltenen Struktur gelöscht werden, geht ihr Pin-Status über die Drop -Implementierung der Strukturen selbst hinaus. Das Projizieren auf ein Feld verspricht, sich nicht innerhalb des Strukturdestruktors von diesem zu entfernen.

Was ist mit dem Namen ElidePin für das automatisch abgeleitete Markermerkmal?

Es würde erfasst, dass der Typ immer so behandelt werden kann, als ob er nicht fixiert wäre oder sich so verhält. Und !ElidePin scheint auch ziemlich klar zu sein, obwohl das subjektiv sein könnte.

Der Standardmarker hat auch keinen perfekten Namen für das Verständnis des Fixierens. Pinned scheint die Vorstellung zu wecken, dass die enthaltende Struktur selbst fixiert ist, aber das Fixieren gilt für Zeiger und erst nach einer expliziten Aktion. Dies ist nicht ganz so kritisch, aber auch nicht unbedeutend, zumal es sich um den Typ handeln kann, der zum ersten Mal in einer Implementierung für einen selbstreferenziellen Typ auftritt.

Mein allgemeiner Widerstand gegen MoveFromUnpin / RemoveFromUnpin ist nur, dass sie viel zu wortreich sind und die Ergonomie manueller Future Implementierungen beeinträchtigen würden. CanUnpin / DePin scheinen mir beide in Ordnung zu sein - ich wünschte, es wäre klarer, dass Unpin dem Muster von Read / Write folgt /usw. aber es scheint, dass das für die Leute nicht intuitiv ist, also werde ich +1 alles, was dies klarer macht, ohne wie syntaktisches Salz auszusehen.

Ich bin damit einverstanden, dass NotWhateverUnpinBecomes wahrscheinlich der beste Name für Pinned . Das heißt, Adhere bedeutet sowohl zu beachten als auch zu bleiben. : leicht_smiling_face:

CanUnpin / DePin scheinen mir beide in Ordnung zu sein - ich wünschte, es wäre klarer, dass Unpin dem Muster von Lesen / Schreiben / etc. Folgt

Ich denke, eines der Dinge, die Unpin schwer machen, im Gegensatz zu Read ist, dass es ein Marker-Merkmal ist. Read ist leicht zu verstehen, da es eine Methode gibt Read::read - alles, was Sie wissen müssen, ist genau dort in trait . Wenn x Read , kann ich x.read() - ähnlich write für Write usw. Es ist schwieriger, das X zu erklären Unpin bedeutet, dass Pin<Ptr<X>> DerefMut Pin<Ptr<X>> implementiert - was bedeutet, dass Sie es so behandeln können, als ob es nur X .

Lesen ist leicht zu verstehen, da es eine Methode Read :: read gibt

Wenn wir nur immer anwendbare Methoden in auto trait Definitionen + GATs hinzufügen könnten, könnten wir Unpin::unpin - fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self> ) haben. ..... beim zweiten Gedanken denke ich nicht, dass das jemanden weniger verwirren wird;)

(Im Ernst, ich würde Pin::unpin dafür unterstützen, von Pin<P<T>> auf P<T> )

Ich würde Pin :: unpin unterstützen, wenn ich von Pin gehe

> bis P.

Dies verwirrt zwei Begriffe in meinem Kopf, genau wie der aktuelle Name selbst. unpin klingt sehr nach einer Umkehrung der Garantien des Typstatus, für einen Vergleich, der so wäre, als gäbe es eine Methode fn unborrow(&'_ T) oder fn unmove(??) . Da pin Garantien bietet, bis ein Speicher, der einen Typ darstellt, Drop::drop ed ist, kehren wir den Status nicht wirklich um. Der Typ garantiert lediglich, dass alle anderen Darstellungen gleichwertige Garantien einhalten, und wir können dies daher ignorieren . Dies ist auch der Hauptunterschied, den ich zwischen Markereigenschaften und so etwas wie io::Read sehe. Eine aktiviert Operationen für den Compiler oder die Sprache, während die andere Operationen für den Programmierer aktiviert.

Darüber hinaus ist es ein wichtiger Punkt, dass dies derzeit nicht genau dargestellt werden kann, wenn nur die richtige Eingabe erfolgt. Wenn Sie die Operation unpin aufrufen, klingt es so, als gäbe es eine umgekehrte Funktion pin . Es ist auch eine Funktion, die leicht fälschlicherweise andeutet, dass eine solche Unpin-Operation irgendwie an Rechenarbeit gebunden ist, zum Beispiel wäre into_pointer() in dieser Hinsicht besser, wenn man auch einer etablierteren Namenskonvention folgt.

Schließlich denke ich, dass es Potenzial für Typen gibt, die speziell eine unpin -Funktion mit Rechenarbeit haben. Ein Typ mit ganz besonderen inneren Invarianten kann möglicherweise seinen inneren Zustand so "reparieren", dass er eine Schnittstelle fn unpin(self: Pin<&'a mut T>) -> &'a mut bietet, an der er bereitwillig alle Garantien von Pin für ein Leben lang einbüßt 'a . In diesem Fall gelten beide oben genannten Punkte nicht mehr. Man könnte sich eine solche Funktion so vorstellen, als hätte sie den gleichen Effekt wie das Ablegen und Rekonstruieren an derselben Speicherstelle (wodurch der Typzustand tatsächlich entfernt wird). Und es kann eine Berechnung beinhalten, z. B. indem einige Selbstreferenzen in dynamische Zuordnungen verschoben werden.

Es wäre bedauerlich, wenn ein verwirrender Name es Bibliotheksdesignern und -implementierern auch erschweren würde, nicht verwirrende Namen zu wählen, indem diese beiden Ideen zusammengeführt werden.

Mein allgemeiner Widerstand gegen MoveFromUnpin / RemoveFromUnpin ist nur, dass sie viel zu wortreich sind und die Ergonomie manueller zukünftiger Implementierungen beeinträchtigen würden.

Ich glaube nicht, dass das stimmt, besonders für MoveFromPin , was für mich vernünftig zu tippen scheint - und bei jeder Art von intelligenter oder dummer Autovervollständigung ist das Problem sowieso meistens nicht vorhanden.

Ein wichtiger Teil der Ergonomie sollte auch die Lesbarkeit und Verständlichkeit des Codes sein. Rust wurde bereits in der Vergangenheit ein wenig kritisiert, weil er Dinge aggressiv abkürzte ( fn , mut usw.), was das Lesen von Code erschwert. Für Dinge, die für ein Konzept, das noch komplizierter zu verstehen ist und das für die meisten Benutzer einen Nischenzweck erfüllt, die Verwendung eines ausführlicheren und aussagekräftigeren Namens völlig akzeptabel sein sollte.

@rfcbot Benennen von Unpin auflösen

Ok, ich habe jetzt schon eine Weile gedünstet und auch persönlich mit Unpin als Markierungsmerkmal wohl fühle. Infolgedessen werde ich einen blockierenden Einwand entfernen (obwohl, wenn andere im libs-Team anders denken, lassen Sie es uns bitte wissen!)

Der Hauptgrund, warum ich zu Unpin gekommen bin, ist im Grunde das, was @cramertj bereits oben gesagt hat. Es ist der idiomatischste Name für dieses Merkmal, das wir uns einfallen lassen konnten. Alle anderen von mir vorgeschlagenen Merkmalsnamen erfüllen nicht die Redewendung, der Unpin gerecht wird (oder zumindest meiner Meinung nach).

Obwohl ich immer noch glaube, dass es bessere Namen für "Ich habe gerade den Namen dieses Merkmals gesehen und möchte wissen, was es tut" gibt, denke ich nicht, dass dies das richtige Problem ist, um es hier zu lösen. An diesem Punkt ist mir klar, dass wir zumindest zu diesem Zeitpunkt keinen anderen Namen für dieses Merkmal finden können, was ebenfalls idiomatisch ist, sondern wir wechseln zu Merkmalnamen, die ein anderes Problem lösen. Das Verständnis von Unpin ist genau wie Send und Sync . Die Dokumentation muss gelesen werden, um vollständig zu verstehen, was los ist.


Als weiteren klarstellenden Punkt habe ich einige "blockierende Einwände" aufgelistet, aber sie sind mehr TODO-Elemente als blockierende Einwände an sich. Ich habe einfach keine großartige Möglichkeit, ansonsten in einem so langen Thread zu navigieren! In diesem Sinne denke ich, dass es in Ordnung ist, eine Stabilisierungs-PR jederzeit zu veröffentlichen, wenn der FCP nun etwa eine Woche lang abgelaufen ist. Die letzten Punkte zu self / pin / pinned können dort kurz besprochen werden (falls erforderlich, können sie als obiger Vorschlag belassen werden).

Dokumentation Ich denke insbesondere ist in diesem Fall keine Voraussetzung für eine Stabilisierung. Wir haben einen vollständigen Zyklus (6 Wochen), um Dokumente hinzuzufügen, bevor diese Typen stabil sind, und wir haben noch mehr Zeit, um die Dokumentation hier zu stützen, bevor die vollständige asynchrone / wartende Story stabil erscheint. Das ist eine Menge Zeit, um zu verbessern, was jetzt da ist, und was jetzt da ist, ist schon ziemlich nützlich!

Was bedeutet hier überhaupt "idiomatisch"? Was ist an Reloc[ate] , Escape , Evade , Pluck oder Roam unidiomatisch? Sie sind alle Verben, und keines von ihnen kann mit dem doppelt negativen Problem verwechselt werden.

@alexcrichton Gibt es einen Grund, warum Unpin als idiomatischer angesehen wird als Depin oder DePin ?

Ich denke, Depin ist eine sehr solide Alternative, und es verursacht mir nicht die gleichen mentalen Schwierigkeiten wie Unpin .

Ein einfacher Grund könnte sein, dass depin kein Wort ist und unpin ist. Persönlich habe ich keine Probleme, Unpin zu verstehen. Ich denke, es passt zur Benennung der anderen Markermerkmale.

@jimmycuadra Es gibt viele Namen in Rust, die keine "echten" Wörter sind, auch in der stdlib.

Es würde mich wundern, wenn dies als wichtiger Grund angesehen würde, einen Namen einem anderen vorzuziehen.

@Pauan Es ist ein wichtiger Grund. Als englischer Muttersprachler klingt Depin für mich so, als hätten wir vergessen, dass das Wort für das Lösen existiert, und versucht, eines zu erfinden. Es scheint mir gramatisch offensichtlich falsch: Das englische Wort für "depinning" ist "unpinning".

Eine gute Analogie wäre, wenn wir APIs hätten, die "Delock" anstelle von "Unlock" sagen.

@jaredr mit "idiomatisch" Ich meine, den etablierten Konventionen der Standardbibliothek zu folgen, die bereits den (zuvor erwähnten) Merkmalen Read und Write . Wir haben eine Konvention von nicht-wortreichen Namen, Verben, die nach Möglichkeit kurz und der Situation angemessen sind.

Namen, wie Sie sie vorgeschlagen haben, sind alle möglich, aber Unpin (oder zumindest glaube ich) ist für diese bestimmte Aktion am besten geeignet (Garantie "Sie können diesen Typ aufheben"). Die anderen, obwohl lose Synonyme von Unpin , werden meiner Meinung nach größtenteils nur vom Wort "unpin" umbenannt und vermitteln manchmal nicht die gleiche Verbindung mit Pin wie Unpin tut.

@Pauan Ich stimme @withoutboats zu, dass Depin nicht so klingt, als würde es genauso gut leben wie Unpin .

@aturon hat in https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636 festgestellt, dass:

Die Verwendung von Pin ist nicht mehr ein Implementierungsdetail als die Wahl von &self gegenüber &mut self ; Auf lange Sicht ist es wahrscheinlich, dass Pin selbst ein separater Referenzmodus mit Sprachunterstützung sein wird. In all diesen Fällen werden in der Signatur die Berechtigungen angegeben, die dem Angerufenen erteilt werden, wobei Pin das Verlassen der Referenz verbietet.

Angeblich bezieht sich dies auf einen nativen &pin Typ oder so ... aber idk, wie dies mit Pin<&mut T> und so quadriert. In meinen Gesprächen mit @withoutboats und insbesondere mit @cramertj waren sie sich überhaupt nicht sicher über die Idee eines separaten Referenzmodus mit Sprachunterstützung und darüber, wie wir von Pin<T> dorthin gelangen könnten.

Bevor Sie pin stabilisieren, ist es ratsam, diese Ansichten miteinander in Einklang zu bringen, um sicherzustellen, dass wir uns auf derselben Seite befinden. dieses kritische Stück Infrastruktur; Deshalb möchte ich vor allem, dass Aaron dies erweitert und dass Boote und Taylor dann abwägen.

Die anderen, obwohl lose Synonyme von Unpin, werden meiner Meinung nach größtenteils nur vom Wort "unpin" umbenannt und vermitteln manchmal nicht die gleiche Verbindung mit Pin wie Unpin.

@alexcrichton das ist eigentlich eine gute sache, denke ich? Wie Send zum Verschieben / Kopieren (=), Sync zum Ausleihen (&). Vielleicht verursacht eine solche Verbindung tatsächlich mehr Verwirrung?

@ crlf0710 es kann! Ich bin mir nicht sicher, ob ich einer solchen Verbindung selbst zustimmen würde. Send und Sync befassen sich mehr mit dem, was sie aktivieren ("Senden" von Typen an andere Threads und "Synchronisieren" des Zugriffs über Threads hinweg), und bei der Benennung haben wir nicht versucht, dies zu tun Vermeiden Sie es, sie in der Nähe der einen oder anderen Operation zu benennen

@alexcrichton genau! Vielleicht sollte es bei diesem Merkmal auch darum gehen, was es ermöglicht. ("ziehen um (Ein Verb hier) aus einem Pin ). Ich bin kein englischer Muttersprachler, aber ich denke immer noch, dass es ein wenig ... seltsam ist, aus einem pin "zu lösen"?

@ crlf0710 Aber was das Merkmal ermöglicht, ist nicht <moving> aus einem Pin, sondern <moving out of a Pin> . Das Problem mit Move und Synonymen für Move ist, dass sie implizieren, dass das Merkmal die Fähigkeit des Typs steuert, sich überhaupt zu bewegen, was nicht der Fall ist. Die Verbindung zu Pin ist wichtig, um zu verstehen, was das Merkmal tatsächlich bewirkt.

Der idiomatischste Name für dieses Merkmal wäre also ein Verb, das "aus einer Stecknadel herausbewegen" bedeutet, für das das Wort "Unnadel" für mich bei weitem am offensichtlichsten ist. Hier ist Wiktionarys Definition von "unpin":

  1. Zum Lösen durch Entfernen eines Stifts.
  2. (Transitive, Computer, grafische Benutzeroberfläche) Zum Trennen (eines Symbols, einer Anwendung usw.) von der Stelle, an der es zuvor fixiert war.

@withoutboats danke fürs erklären! Eigentlich denke ich, ich kann diesen Unpin Namen akzeptieren oder irgendein Name, über den sich das Rostteam endgültig entscheidet. Ich möchte die Stabilisierung der Pin-Typen überhaupt nicht blockieren und ich verstehe die Bedenken bezüglich "Bewegen" usw. absolut.

Es fühlt sich einfach so an, als gäbe es irgendwo eine kleinste "Wiederholung". und ich muss mich selbst überzeugen: es bedeutet nicht "nicht steckbar", sondern das Gegenteil. Ich denke, nach einiger Zeit werde ich mich daran gewöhnen, wenn das die endgültige Entscheidung ist.

Erlauben Sie mir bitte gleichzeitig, meinen letzten Vorschlag zu machen: Eigentlich finde ich das oben erwähnte Verb Detach auch ganz nett, wenn auch etwas zu allgemein. Wie gesagt, ich bin kein Muttersprachler, also kann ich nicht für andere sprechen. Bitte sehen Sie es nur als eine kleine Idee.

Ich dachte über sichere disjunkte fixierte Projektionen nach und kam auf die Idee, die dies ermöglichen könnte. Dies ist ein Makro zur Simulation des Abgleichs mit fixierten veränderlichen Werten. Mit diesem Makro können wir schreiben

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

um ein Pin<&mut MyStruct> in angeheftete veränderbare Verweise auf die Felder zu zerlegen.

Um dies sicher und nutzbar zu machen, benötigen wir zwei weitere Dinge:

  • Der Typ Kite zum Markieren der Felder "always- Unpin "
  • Machen Sie Unpin unsicher

Letzteres wird für die Sicherheit der Projektion benötigt. Ohne dies können wir " Kite mit angehefteten Projektionen" sicher definieren, was eigentlich falsch ist.

In diesem Sinne schlage ich vor, Unpin vor der Stabilisierung unsicher zu machen, um einen Raum für andere nützliche Operationen zu lassen, um sicher zu sein.

@qnighy Es ist möglich, dass ein Typ in seiner Drop -Implementierung gegen Pinning-Garantien verstößt, wodurch Drop gleich stark wie Unpin . Wenn Sie Unpin unsicher machen, wird kein zusätzlicher Code sicher, da Drop ebenfalls sicher ist und dies nicht geändert werden kann. Wir haben viel über das Tracking-Problem gesprochen, und es wurde auch in diesem Thread erwähnt.

Grundsätzlich können Stiftprojektionen nicht sicher gemacht werden, ohne bestimmte negative Grenzen geltend machen zu können, die heute in Rust nicht ausgedrückt werden können.

@ crlf0710

Erlauben Sie mir bitte gleichzeitig, meinen letzten Vorschlag zu machen: Eigentlich finde ich das oben erwähnte Detach-Verb auch ganz nett, wenn auch etwas zu allgemein. Wie gesagt, ich bin kein Muttersprachler, also kann ich nicht für andere sprechen. Bitte sehen Sie es nur als eine kleine Idee.

Das Trennen scheint viel besser zu sein als Namen wie Verschieben und Verschieben, aber es scheint im Widerspruch zu anderen möglichen Verwendungen der Metapher zum Trennen zu stehen (ähnlich wie Escape mit "Escape-Analyse" usw. in Konflikt geraten könnte). Es gibt nur so viele Metaphern darüber, wie Daten mit anderen Daten in der Informatik in Beziehung gesetzt werden können, was mich an einen weiteren neuen Vorteil von Unpin denken lässt: Indem ich mich eng an die "Pin" -Metapher klammere, nimmt sie keinen Platz für zukünftige metaphorische Sprache ein Möglicherweise müssen wir sie für einen anderen Zweck verwenden, so wie es Namen wie "Trennen", "Escape" oder "Verschieben" könnten.

@withoutboats Ich habe keine besondere Vorliebe für einen Namen oder eine große Investition in dieses Fahrrad ... aber ich bin neugierig; Welchen anderen Zweck würde Detach passen (wenn Sie spekulieren ...)?

@Centril Ich habe keinen klaren Anwendungsfall im Sinn, aber ich könnte mir einen Anwendungsfall vorstellen, der zum Beispiel mit der Destrukturierung zusammenhängt.

@withoutboats Ja das macht Sinn; Prost!

@withoutboats Ich bin mir nicht sicher, ob der Wiktionary-Eintrag die beste Motivation ist, um die Benennung von Unpin zu rechtfertigen, um move from a pin zu aktivieren. Die Metaphern der physischen Repräsentation leiden unter der Tatsache, dass das Bewegen in Rust kein Objekt in der Welt wegnimmt. Genauer gesagt, das "Upinning" eines angehefteten Zeigers gibt mir keine Kontrolle über den referenzierten Speicher, seine Zuordnung und Gültigkeit als Objektdarstellung von T sind weiterhin erforderlich und werden durch die Zeigersemantik garantiert. Daher ergibt das Aufheben des Fixierens keine vollständig kontrollierte Darstellung von T , wie dies beim Entpacken der Fall wäre. Und ein Wörterbucheintrag, der unpinning in Bezug auf moving ist in der Tat mehr Teil der Verwirrung als die Rechtfertigung eines solchen Namens.

In der vorgeschlagenen API hat keine der Methoden einen solchen Effekt (und sie liegt auch nicht im Bereich von Pin). Ich sehe zum Beispiel keinen sicheren Weg, um Pin<Box<T>> in Box<T> (und damit auch in T ) umzuwandeln, und ich bin mir auch nicht sicher, ob Unpin sollte habe so starke Möglichkeiten. Ich bin mir nicht ganz sicher, wo der Unterschied liegen würde. Aber soweit ich verstanden habe, gilt Pin für einen Speicherort, während Unpin garantiert, dass nichts in der Darstellung von T auf Pin-Garantien beruht. Wäre das jedoch dasselbe, als könnte man die Erinnerung vollständig herausnehmen und vergessen? Ich denke nicht. Ich werde mir ein konkreteres Beispiel vorstellen.

Bearbeiten: Konkreter kann ich mich darauf verlassen, dass eine Instanz von T::drop(&mut) in dem angehefteten Speicher aufgerufen wird, bevor dieser Speicher freigegeben wird, selbst wenn T Unpin ist? Soweit ich aus dem Formalismus ersehen kann, sollte die Antwort ja sein, aber der Name Unpin teilt mir das Gegenteil mit.

Bearbeiten 2: Mit Rc kann man Pin<&T> aber für T: Unpin immer noch nicht drop am ursprünglichen Speicherort aufgerufen werden. Halten Sie eine Referenz außerhalb des Pins am Leben. Nachdem der Pin fallen gelassen wurde, können Sie mit Rc::try_unwrap ausziehen. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca Das beantwortet die Frage effektiv über vorhandene Mechanismen, funktioniert aber wie beabsichtigt?

Vielleicht IgnorePin ? Wenn T IgnorePin , können Sie Pin<Box<T>> als &mut T wobei das Vorhandensein von Pin Wesentlichen ignoriert wird (AIUI ignoriert auch die Box ). Ignorieren ist ein Verb, ich denke, IgnorePin ist es nicht, aber ich bin mir nicht sicher, was es ist. IgnorePin beschreibt, was es erlaubt, es beschreibt nicht die Einschränkungen, die dem Typ auferlegt wurden, aber auch nicht Unpin .

@wmanley Ich hatte eine sehr ähnliche Idee, ElidePin , in einem Kommentar oben, obwohl ich damals nicht konkret ausdrücken konnte, warum sich diese präziser anfühlte. Aber ich stimme zu, dass 'ein Verb' der Styleguide für Rust-Marker-Merkmale ist. Obwohl es auch eine natürlichere Negation in Form von !ElidePin / !IgnorePin erlaubt, ist es nicht optimal.

@withoutboats Folgefrage : Wie interagiert Pin mit ZST, da Pin in Bezug auf den zugrunde liegenden Speicher angegeben zu sein scheint? Da Pinned eine ZST ist, kann sogar eine ZST entweder Unpin oder nicht. Ich würde intuitiv sagen, dass seine Speicherdarstellung niemals formal ungültig wird, daher muss T::drop(&mut self) niemals aufgerufen werden, ganz ähnlich wie man einen Pin aus &mut 'static _ Speicher erstellen könnte, zum Beispiel aus einem durchgesickerten Box . Gleichzeitig kann das alles falsch sein und ich sehe, wie eine andere Interpretation möglich wäre. Ich bin der Meinung, dass diese Einstellungen in der Dokumentation Beachtung verdienen.

Ist es sicher, ein Pin<P<T>> mit einem T: !Unpin zu erstellen und dann den Wert sofort zu entfernen, wenn garantiert ist, dass nichts das Fixieren beobachtet hat? Ist es nur ein undefiniertes Verhalten, wenn der angeheftete Wert verschoben wird, nachdem eine abfrageartige Methode aufgerufen wurde?

@HeroicKatora Leider ist es heute möglich, Drop für einen Typ zu implementieren, der aufgrund von Generika !Unpin sein könnte, z.

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

@cramertj Danke, habe das auch gerade gemerkt.

@cramertj Also verwenden wir standardmäßig T: ?Unpin für generische Grenzen? Gibt es einen weiteren Grund dafür, dass für vorhandene Typen wie Sized nicht standardmäßig T: Unpin Sized ? Es würde einige Fälle geben, in denen dies ärgerlich streng wäre, aber kann diese zusätzliche automatische Bindung Regressionen verursachen?

@HeroicKatora Dazu müssten für jeden Typ in der Standardbibliothek und für eine Reihe von Codes, die sich überhaupt nicht um Unpin kümmern, ?Unpin Grenzen gesetzt werden.

Es ist auch sicher, ein PhantomPinned-Feld hinzuzufügen, um einen Drop +! Unpin-Typ zu erstellen.

Gibt es einen anderen Grund dafür, dass T nicht standardmäßig verwendet wird: Für vorhandene Typen entfernen?

Unpin ist nur eine automatische Eigenschaft wie Senden und Synchronisieren. Dieser Vorschlag enthält keine neuen Sprachfunktionen. Es hat also die gleiche Semantik wie die bereits definierten automatischen Merkmale, dh, sie werden standardmäßig nicht auf Generika angewendet (im Gegensatz zu Sized, einem integrierten Merkmal des Compilers, kein automatisches Merkmal).

Ist es nur ein undefiniertes Verhalten, wenn der angeheftete Wert verschoben wird, nachdem eine abfrageartige Methode aufgerufen wurde?

Wir sollten hier klarstellen, dass undefiniertes Verhalten in diesem Zusammenhang nicht wirklich die traditionelle Art von UB ist. Vielmehr kann dieser Code, der einen Typ in einem Pin beobachtet, Annahmen über die Besitzersemantik dieses Werts treffen (dh, wenn er Unpin nicht implementiert, wird er erst wieder verschoben oder ungültig gemacht, nachdem sein Destruktor ausgeführt wurde). Es ist nicht UB in dem Sinne, dass es vom Compiler angenommen wird, dass es niemals passiert (der Compiler weiß nicht, was ein Pin ist).

Also ja, in dem Sinne, dass Sie überprüfen können, ob kein Code auf der von Ihnen bereitgestellten Garantie beruht. Aber auch das, was Sie getan haben, ist sicherlich sinnlos, da kein Code feststellen konnte, dass der Typ fixiert war.

@cramertj Ich Pin mit Drop in der Spezifikation, aber nicht in der Sprache. Ein ziemlicher Konflikt zwischen der Korrektheit der Benutzeroberfläche, der Benutzerfreundlichkeit und der Veröffentlichung in naher Zukunft. Kann ich oben eine Klarstellung zu Edit 2 erhalten:

Mit Rc kann man Pin <& T> beobachten, aber für T: Unpin hat noch kein Drop am ursprünglichen Speicherort aufgerufen. Halten Sie eine Referenz außerhalb des Pins am Leben. Nachdem der Pin fallen gelassen wurde, können Sie mit Rc :: try_unwrap ausziehen. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca

@HeroicKatora Das Problem mit Ihrem Code ist, dass Mem<'a>: Unpin , aber die Zeile mit unsicherem Code, die Sie verwenden, basiert auf Annahmen, die nur für Typen gelten, die Unpin nicht implementieren. Ich habe Ihren Inhalt so bearbeitet, dass er den Beweis enthält, dass Mem<'a>: Unpin : https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=201d1ae382f590be8c5cac13af279aff

Sie verlassen sich auf die Unpin-Bindung, wenn Sie Pin::new aufrufen. Diese kann nur für Zeiger aufgerufen werden, deren Ziel Unpin implementiert.

Die Lücke, von der Sie dachten, dass Sie sie gefunden haben, wurde in Betracht gezogen, weshalb es keinen Weg gibt, von einem Rc<T: ?Unpin> zu einem Pin<Rc<T>> . Sie müssen den Rc im Pin mit Rc::pinned konstruieren.

@withoutboats Ich wollte nur bestätigen, dass T: Unpin tatsächlich auch den Aufruf von drop vor der Ungültigmachung ablehnt. Das wirft die Frage auf, sollte es nicht auch geben

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

Da es keinen Teil der Benutzeroberfläche schützt und das Auspacken von Pin<Box<T>> in Box<T> möglicherweise nützlich ist (natürlich für T: Unpin ).

@HeroicKatora Sie haben in diesem Moment stabilisieren, da dieser Thread bereits Hunderte von Kommentaren lang ist. Wir können es später jederzeit hinzufügen, wenn dies erforderlich ist, genau wie bei allen Standard-APIs.

Ich würde nur sagen, dass sowohl die Benennung als auch die Funktionalität von Unpin bei dieser umgekehrten Methode viel sinnvoller sind. Und zu wissen, dass die Pin Garantien tatsächlich auf T: !Unpin . Dies würde auch direkt durch die Existenz einer solchen Methode demonstriert, anstatt Notluken zu konstruieren, um die Möglichkeit aufzuzeigen: Lächeln :. Und seine Dokumentation wäre ein perfekter Ort, um die Einschränkungen zu erklären (oder noch einmal zu verknüpfen). (Man könnte sogar in Betracht ziehen, es unpin anstelle von into_inner ).

Bearbeiten: Es würde meist nur verallgemeinern, was bereits da ist.

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

  • fn unpin(self) -> P

hat die Instanziierung für P=&'a mut T , die dem vorgeschlagenen entspricht

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

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

Nachdem die Stabilisierung abgeschlossen ist, scheint der Punkt des PFCP umstritten zu sein, daher:

@rfcbot abbrechen

Gibt es ein Tracking, um sicherzustellen, dass die Kommentare, die ab https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982 erstellt wurden , in Dokumente umgewandelt werden? Scheint, wir sind bereits zu spät für die Veröffentlichung, da die Beta verzweigt wurde ... obwohl dies hier ausdrücklich vor der Stabilisierung angekündigt wurde: /

Gibt es einen Grund, warum dies noch offen ist? @Centril du hast das geschlossen und wieder geöffnet, war das absichtlich?

@RalfJung Ich habe versucht, den FCP abzubrechen, aber er hat nicht @alexcrichton können Sie den FCP kündigen?

Dies ist stabil, also schließen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen