@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.)
[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
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.
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>>
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:
(Foo: Unpin) implies (Bar: Unpin)
: dasFoo
(der enthaltende Typ) Unpin
während istBar
(der projizierte Typ) ist nicht Unpin
.Bar
während der Zerstörung von Foo
niemals bewegt wird.Foo
keinen Destruktor hat oder der Destruktor vorsichtig istFoo
(der enthaltende Typ) nicht repr(packed)
,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.
Unpin
aus dem Vorspiel, entferne pin::Unpin
ReexportIn 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
.
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.
get_mut_unchecked
in get_unchecked_mut
Die aktuelle Reihenfolge stimmt nicht mit anderen Verwendungen in der Standardbibliothek überein.
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.
Pin
als repr(transparent)
Der Pin sollte eine transparente Hülle um den Zeiger sein, mit der gleichen Darstellung.
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
@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:
Sorgen:
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:
&mut P::Target
von Pin<P>
, dürfen Sie niemals P::Target
bewegen.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:
impl core::future::future::Future+futures_core::future::FusedFuture
ist das Merkmal std::marker::Unpin
für std::marker::Pinned
nicht implementiertPin
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 .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.drop()
-Methode jetzt wieder &mut self
anstelle von Pin
.Pin
und Unpin
tatsächlich sind. Dies scheint ein bisschen ein Leck an Implementierungsdetails für einige speziellere Anwendungsfälle zu sein.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. ;)
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, ohnedrop
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>
.
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
verstehenSchamloser 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.
@ 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.)
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
Pin<P>
zu konstruieren, und zwar während der Existenz vonPin<P>
bedeutet normalerweise, dass P::Target
nie wieder in der verschoben wirdP::Target
implementiert Unpin
das "die Garantien vonPin
nicht mehr halten ". Infolgedessen ist die Sicherheit hier, weil es keine gibtunsafe fn new_unchecked(P) -> Pin<P> where P: Deref
unsafe
da P
dies nicht tutUnpin
, also muss Pin<P>
die Garantie dafür einhaltenP::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
Pin<P>
wir die Garantie, dass sich P::Target
niemals bewegen wird. Das istPin
. Infolgedessen bedeutet es trivial, dass&P::Target
, ein weiterer "intelligenter Zeiger" auf P::Target
wird dasselbe liefern&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
as_ref
.Pin
, also kann nichts einfach seinFrage : 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
Pin<P>
(und der Tatsache, dass wir nichts darüber wissenUnpin
), sollte dies nicht garantieren, dass sich P::Target
niemals bewegt? Wenn wir könnenP::Target
. Sollte dies nicht unsicher sein?unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>
unsafe
-Funktion, daher lautet die Hauptfrage hier: "Warum ist das nicht so?"...
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
Pin<&T>
bedeutet, dass sich T
niemals bewegen wird. Rückgabe von &T
T
, daher sollte es sicher sein, dies während der Aufrechterhaltung zu tunEin "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>
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
mem::replace
für das Ergebnis verwenden können, um T
(sicher) zu verschieben. Dasunsafe
hier "obwohl ich dir &mut T
gebe, darfst du das nieT
".unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>
unsafe
hier sind im Grunde mindestens die gleichen wie oben, wir sind&mut T
(kann keinen unsicheren Verschluss erfordern), was möglich istmem::replace
. Es gibt wahrscheinlich auch andere Unsicherheiten hierfn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin
Unpin
sagt ein Typ " Pin<&mut T>
hat keine Garantien, es ist&mut T
". Infolgedessen ohne Garantie dafür&mut T
sicher zurückgebenimpl<P: Deref> Deref for Pin<P> { type Target = P::Target }
as_ref
gefolgt von get_ref
implementiert werdenimpl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }
as_mut
gefolgt von get_mut
implementiert werdenimpl<T: ?Sized> Unpin for Box<T>
(und andere zeigerbezogene Implementierungen)
Box<T>
das Merkmal Unpin
implementierenT
Unpin
implementiert hat. Diese Implementierung hier kodifiziert das sogarT
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:
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).
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?
Welche Art von APIs oder Typsystem-Ergänzungen werden durch die Stabilisierung der Pinning-API unmöglich? (Diese Frage ist eine Erweiterung von 2.)
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:
Die Dokumentation in der Standardbibliothek scheint recht spärlich. :(
Ich stimme anderen zu, dass das Pinning-Konstrukt ziemlich mental anstrengend / komplex ist.
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, dieUnpin
nicht implementiert. es erlaubt dir
Unterdrücken Sie die automatische Implementierung vonUnpin
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 AdressePin<&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:
Pin
schützt nur "eine Ebene tief".Pin::new_unchecked
schränkt das Gerät für Deref
und DerefMut
.Pin
und Drop
.!Unpin
sich nur einmal bewegen darf, wenn er in einem Pin
.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 vonP
nicht bewegt, bis es fallen gelassen wird. Die Ausnahme ist, wenn das Ziel vonP
Escape
. MitEscape
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 einemPin
"entkommen".Escape
ist ein Auto-Merkmal, daher sind alle Typen, die vollständig aus Typen bestehen, dieEscape
sind, selbst auchEscape
. Implementierer können dies jeden Abend mitimpl !Escape for T {}
oder durch Einfügen des MarkersNoEscape
vonstd::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 SieRelocate
indem Sie Ihrem Typimpl !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:
Unpin
Grenzen werden angezeigt, weil Sie beim Abrufen tatsächlich eine Zukunft verschieben müssen (z. B. Dinge wie einige ausgewählte APIs).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:
Sie haben einen Typ, der gegenüber anderen Typen generisch ist (z. B. struct Foo<A>
).
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)]
oderimpl 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.
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 wieMove
- 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:
P
DerefMut
"vernünftiges Verhalten" nicht auf Pin::new_unchecked
dokumentiert.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.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.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.
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.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.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, dassPin
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, wobeiPin
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":
- Zum Lösen durch Entfernen eines Stifts.
- (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:
Kite
zum Markieren der Felder "always- Unpin
"Unpin
unsicherLetzteres 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.
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.