Rust: Tracking-Problem für Pin-APIs (RFC 2349)

Erstellt am 18. März 2018  ·  211Kommentare  ·  Quelle: rust-lang/rust

Tracking-Problem für rust-lang / rfcs # 2349

Blockstabilisierung:

  • [x] Implementierung (PR # 49058)
  • [] Dokumentation

Ungelöste Fragen:

  • [] Sollten wir stärkere Garantien für das Auslaufen von !Unpin Daten geben?

Bearbeiten : Zusammenfassender Kommentar: https://github.com/rust-lang/rust/issues/49150#issuecomment -409939585 (im standardmäßig ausgeblendeten Teil)

B-RFC-approved C-tracking-issue T-lang T-libs

Hilfreichster Kommentar

@rfcbot betrifft API-Refactor

Ein bisschen Inspirationsstruktur und letzte Nacht habe ich herausgefunden, wie wir diese API so umgestalten können, dass es nur einen Pin -Typ gibt, der einen Zeiger umschließt, anstatt für jeden einzelnen Zeiger eine angeheftete Version erstellen zu müssen. Dies ist in keiner Weise eine grundlegende Umgestaltung der API, aber es fühlt sich besser an, die Komponente "Pins the Memory" zu einem zusammensetzbaren Teil herauszuziehen.

Alle 211 Kommentare

Ich stelle jetzt fest, dass das Pinning von Stapeln nicht Teil des RFC ist.

@ Nemo157 Du solltest es kopieren und über deine Erfahrungen berichten!

Die ungelöste Frage nach dem Verlust von Unpin -Daten bezieht sich darauf. Diese API ist nicht in Ordnung, wenn wir sagen, dass Sie Unpin -Daten in einem Pin nicht überschreiben können, es sei denn, der Destruktor wird ausgeführt, wie von @cramertj angefordert. Es gibt andere, weniger ergonomische Stack-Pinning-APIs, die dafür geeignet sind. Es ist unklar, was hier die richtige Wahl ist - ist die ergonomische Stapel-Pinning-API nützlicher oder ist die zusätzliche Garantie für Leckagen nützlicher?

Eine Sache, die ich beachten werde, ist, dass das Stapeln des Stapels für Dinge wie Future::poll innerhalb des Makros await! nicht ausreichte, da wir nicht in einer Schleife abfragen konnten. Es würde mich interessieren, ob Sie auf diese Probleme stoßen und wie / ob Sie sie lösen, wenn Sie dies tun.

Meine derzeitige Verwendung ist ziemlich trivial, ein Single-Threaded-Executor, der ein einzelnes StableFuture ohne Spawn-Unterstützung ausführt . Ein Wechsel zu einer API wie @cramertj schlägt vor, dass dies gut funktioniert. Ich habe mich gefragt, wie ich dies erweitern kann, um das Laichen mehrerer StableFuture , aber zumindest bei meinem aktuellen Projekt ist dies nicht erforderlich.

Ich habe gerade versucht, mit der API zu experimentieren. Sieht so aus, als ob die folgende (von RFC vorgeschlagene) Definition von Future nicht mehr objektsicher ist?

trait Future {
    type Item;
    type Error;

    fn poll(self: Pin<Self>, cx: &mut task::Context) -> Poll<Self::Item, Self::Error>;
}

Keine Ursache. Es wurde eine Notiz über einen Plan gefunden, mit dem arbitrary_self_types objektsicher gemacht werden soll.

@ohne Boote

Eine Sache, die ich beachten werde, ist, dass das Stapeln des Stapels für Dinge wie Future :: poll innerhalb des Wartens nicht ausreichte! Makro, weil es uns nicht erlaubt hat, in einer Schleife abzufragen.

Könnten Sie das näher erläutern?

@RalfJung Sie benötigen Pin , um das Ausleihen als Pin , was derzeit nicht der Fall ist.

@cramertj Das klingt nach einer Einschränkung der Pin API, nicht der Stack Pinning API?

@RalfJung Ja, das stimmt. PinBox kann jedoch als Pin , während der Typ mit Stapel-Pinning dies nicht kann (ein Ausleihen als Pin erstellt einen Kredit für die gesamte Lebensdauer des Stapeltyps).

Bei einem Pin kann ich es als &mut Pin ausleihen und dann Pin::borrow - das ist eine Form der Neuaufnahme. Ich nehme an, das ist nicht die Art von Reborowing, von der Sie sprechen?

@RalfJung No-- Methoden wie Future::poll sollten self: Pin<Self> anstelle von self: &mut Pin<Self> (was kein gültiger self -Typ ist, da dies nicht der Fall ist) t Deref<item = Self> - es ist Deref<Item = Pin<Self>> ).

Es könnte der Fall sein, dass wir dies tatsächlich mit Pin::borrow Laufen bringen könnten. Ich bin mir nicht sicher.

@cramertj Ich habe nicht vorgeschlagen, poll auf x: &mut Pin<Self> anzurufen; Ich dachte an x.borrow().poll() .

@ RalfJung Oh, ich

Ich werde versuchen, mich daran zu erinnern, ein Beispiel für einige Dinge zu veröffentlichen, die ich nächste Woche mit Pin s mache, soweit ich sagen kann, dass das Ausleihen perfekt funktioniert. Ich habe eine angeheftete Version des Merkmals futures::io::AsyncRead zusammen mit funktionierenden Adaptern wie fn read_exact<'a, 'b, R: PinRead + 'a>(read: Pin<'a, R>, buf: &'b [u8]) -> impl StableFuture + 'a + 'b und kann daraus ein relativ komplexes StableFuture , das nur oben angeheftet ist Niveau.

Hier ist das vollständige Beispiel für das, was ich zum Lesen verwende:

pub trait Read {
    type Error;

    fn poll_read(
        self: Pin<Self>,
        cx: &mut task::Context,
        buf: &mut [u8],
    ) -> Poll<usize, Self::Error>;
}

pub fn read_exact<'a, 'b: 'a, R: Read + 'a>(
    mut this: Pin<'a, R>,
    buf: &'b mut [u8],
) -> impl StableFuture<Item = (), Error = Error<R::Error>>
         + Captures<'a>
         + Captures<'b> {
    async_block_pinned! {
        let mut position = 0;
        while position < buf.len() {
            let amount = await!(poll_fn(|cx| {
                Pin::borrow(&mut this).poll_read(cx, &mut buf[position..])
            }))?;
            position += amount;
            if amount == 0 {
                Err(Error::UnexpectedEof)?;
            }
        }
        Ok(())
    }
}

Dies ist etwas ärgerlich, da Sie Instanzen überall als Pin durchlaufen und Pin::borrow wenn Sie Funktionen aufrufen.

#[async]
fn foo<'a, R>(source: Pin<'a, R>) -> Result<!, Error> where R: Read + 'a {
    loop {
        let mut buffer = [0; 8];
        await!(read_exact(Pin::borrow(&mut source), &mut buffer[..]));
        // do something with buffer
    }
}

Ich hatte nur den Gedanken, dass ich impl<'a, R> Read for Pin<'a R> where R: Read + 'a umgehen könnte, um zu umgehen, dass ich überall Werte als Pin<'a, R> , stattdessen könnte ich fn foo<R>(source: R) where R: Read + Unpin . Leider schlägt dies fehl, weil Pin<'a, R>: !Unpin . Ich denke, es ist sicher, ein unsafe impl<'a, T> Unpin for Pin<'a, T> {} hinzuzufügen, da der Pin selbst nur eine Referenz ist und die Daten dahinter immer noch fixiert sind.

Bedenken: Es ist wahrscheinlich, dass die meisten Typen in libstd Unpin bedingungslos implementieren sollen, auch wenn ihre Typparameter nicht Pin . Beispiele sind Vec , VecDeque , Box , Cell , RefCell , Mutex , RwLock , Rc , Arc . Ich gehe davon aus, dass die meisten Kisten überhaupt nicht an das Feststecken denken und daher ihre Typen nur dann Unpin wenn alle Felder Unpin . Das ist eine gute Wahl, führt aber zu unnötig schwachen Schnittstellen.

Wird sich dies von selbst lösen, wenn wir sicherstellen, dass Unpin für alle libstd-Zeigertypen (möglicherweise sogar einschließlich Rohzeiger) und UnsafeCell implementiert werden? Wollen wir das tun?

Wird sich dies von selbst lösen, wenn wir sicherstellen, dass Unpin für alle libstd-Zeigertypen (möglicherweise sogar einschließlich Rohzeiger) und UnsafeCell implementiert wird? Wollen wir das tun?

Ja, es scheint mir die gleiche Situation wie Send zu sein.

Mir ist gerade eine neue Frage gekommen: Wann sind Pin und PinBox Send ? Im Moment macht der Auto-Trait-Mechanismus sie zu Send wenn T Send . Es gibt keinen a priori Grund, dies zu tun; Genau wie Typen im gemeinsam genutzten Typstatus ein eigenes Markierungsmerkmal für die Sendefähigkeit haben (genannt Sync ), könnten wir ein Markierungsmerkmal erstellen, das besagt, wenn Pin<T> Send , z. B. PinSend . Im Prinzip ist es möglich, Typen zu schreiben, die Send aber nicht PinSend und umgekehrt.

@RalfJung Pin ist Senden, wenn &mut T Senden ist. PinBox ist Senden, wenn Box<T> Senden ist. Ich sehe keinen Grund für sie, anders zu sein.

Nun, genau wie einige Typen Send aber nicht Sync , könnten Sie einen Typ haben, der sich auf "Sobald diese Methode mit Pin<Self> aufgerufen wird, kann ich mich darauf verlassen, dass sie niemals verschoben wird." zu einem anderen Thread ". Dies könnte beispielsweise zu Futures führen, die vor dem ersten Start gesendet werden können, dann aber in einem Thread verbleiben müssen (ähnlich wie sie vor dem Start verschoben werden können, dann aber fixiert bleiben müssen). Ich bin mir nicht sicher, ob ich ein überzeugendes Beispiel finden kann, vielleicht etwas über eine Zukunft, die threadlokalen Speicher verwendet?

Ich habe gerade das von @Diggsey erwähnte Problem auf Pin<Option<T>> -> Option<Pin<T>> sollte eine sichere Operation sein, aber es scheint nicht möglich zu sein, sie zu implementieren, selbst wenn die aktuellen unsicheren APIs verwendet werden, geschweige denn, welche Art von API erforderlich wäre, um diesen sicheren Code zu erstellen:

trait OptionAsPin<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>>;
}

impl<T> OptionAsPin<T> for Option<T> {
    fn as_pin<'a>(self: Pin<'a, Self>) -> Option<Pin<'a, T>> {
        match *unsafe { Pin::get_mut(&mut self) } {
            Some(ref mut item) => Some(unsafe { Pin::new_unchecked(item) }),
            None => None,
        }
    }
}

(Es ist möglich, mit Transmute zu umgehen, um die Lebenszeiten zu erzwingen, aber das macht mich viel zu eklig).

Ich möchte eine ungelöste Frage hinzufügen: Sollten wir einen gemeinsam genutzten angehefteten Referenztyp wieder hinzufügen? Ich denke die Antwort ist ja. Weitere Details finden Sie in diesem Beitrag mit Diskussion .

Ich habe gerade gelesen, dass Futures 0.2 nicht so endgültig ist, wie ich dachte , also ist es vielleicht immer noch möglich, Pin wieder in PinMut umzubenennen und eine gemeinsame Version hinzuzufügen.

@RalfJung Ich habe Ihren Blog-Beitrag noch einmal gründlicher gelesen, um die von Ihnen vorgeschlagenen Änderungen zu verstehen.

Ich denke, Sie haben einen potenziell überzeugenden Anwendungsfall für eine unveränderliche Pin-Variante gefunden, aber ich verstehe Ihre Kommentare zu Deref und &Pin<T> <=> &&T . Selbst wenn Pin<T> sicher auf &T herabgesetzt werden kann, sind sie nicht gleichwertig, da &T nicht auf Pin<T> . Ich sehe keinen Grund, die sichere Konvertierung unsicher zu machen (indem das sichere Deref impl eliminiert wird).

Derzeit hat die map -Methode für Pin die Signatur

pub unsafe fn map<U, F>(this: &'b mut Pin<'a, T>, f: F) -> Pin<'b, U>

Was ist der Grund, warum es nicht der folgende ist?

pub unsafe fn map<U, F>(this: Pin<'a, T>, f: F) -> Pin<'a, U>

Derzeit kann ich ein Pin eines Typs nicht in ein Pin eines seiner Felder umwandeln, ohne die Lebensdauer unnötig zu verkürzen.

Ein weiteres Problem mit der map -Methode besteht darin, dass es unmöglich zu sein scheint, ein Pin einer Struktur in zwei Pin zu verwandeln, die jeweils ein anderes Feld der Struktur aufweisen. Was ist der richtige Weg, um das zu erreichen?

Ich habe dieses Makro dafür verwendet:

macro_rules! pin_fields {
    ($pin:expr, ($($field:ident),+ $(,)?)) => {
        unsafe {
            let s = Pin::get_mut(&mut $pin);
            ($(Pin::new_unchecked(&mut s.$field),)+)
        }
    };
}

In vielen Diskussionen um Pin scheint die Annahme zu bestehen, dass das "Projizieren" eines Pins auf ein privates Feld als sicher angesehen werden sollte. Ich denke nicht, dass das ganz stimmt. Zum Beispiel lautet der Dokumentkommentar zu map derzeit:

Sie müssen sicherstellen, dass sich die von Ihnen zurückgegebenen Daten nicht verschieben, solange sich der Argumentwert nicht bewegt (z. B. weil es eines der Felder dieses Werts ist), und dass Sie sich nicht aus dem Argument herausbewegen, zu dem Sie empfangen die Innenfunktion.

Die Garantie, dass "die von Ihnen zurückgegebenen Daten nicht verschoben werden, solange sich der Argumentwert nicht bewegt", ist die korrekte Beschreibung des Vertrags, den ein Anrufer von map einhalten muss. Die Klammer "(zum Beispiel, weil es eines der Felder dieses Werts ist)" scheint zu implizieren, dass Sie garantiert sicher sind, solange Sie nur einen Verweis auf Ihr eigenes privates Feld zurückgeben. Dies ist jedoch nicht der Fall, wenn Sie Drop implementieren. Ein Drop Impl wird ein &mut self , selbst wenn anderer Code bereits ein Pin<Self> . Es kann fortfahren, mem::replace oder mem::swap zu verwenden, um seine Felder zu verlassen, was gegen das Versprechen verstößt, das durch eine frühere "korrekte" Verwendung von map .

Mit anderen Worten: Wenn Sie einen "korrekten Pin-Projektions" -Aufruf auf Pin::map (der Aufruf sieht aus wie unsafe { Pin::map(&mut self, |x| &mut x.p) } ), ohne andere Aufrufe von unsafe , kann man unsound / undefined erzeugen Verhalten. Hier ist ein Spielplatz-Link, der dies demonstriert.

Dies bedeutet nicht, dass mit der aktuellen API etwas nicht stimmt. Es zeigt, dass Pin::map als unsafe markiert werden sollte, was es bereits ist. Ich glaube auch nicht, dass die Gefahr groß ist, dass Menschen bei der Implementierung von Futures oder Ähnlichem versehentlich darüber stolpern - Sie müssen wirklich alles daran setzen, um mit einem Drop Impl aus Ihren eigenen Feldern herauszukommen, und Ich bezweifle, dass es viele praktische Gründe gibt, dies zu tun.

Ich denke jedoch, dass der Dokumentkommentar für map dies erwähnen möchte, und ich denke auch, dass er einige Ideen für Erweiterungen ungültig macht, die ich im RFC und in den umliegenden Diskussionen sehe:

  • Es sollte kein Makro / Ableiten geben, das "Pin-Projektion" ausführt und es sicher aussehen lässt. Die Projektion legt dem anderen Code, der ihn umgibt, tatsächlich einen Vertrag auf, den der Compiler nicht vollständig durchsetzen kann. Daher sollte die Verwendung des Schlüsselworts unsafe erforderlich sein, wenn dies erledigt ist.
  • Der RFC erwähnt, dass es "trivial wäre, durch Felder zu projizieren", wenn Pin in eine &'a pin T Sprachfunktion umgewandelt würde. Ich glaube, ich habe gezeigt, dass dies immer noch unsafe erfordern sollte, auch wenn es auf die Projektion privater Felder beschränkt ist.

@ohne Boote

Auch wenn Pinkann sicher auf & T heruntergeworfen werden, was sie nicht gleichwertig macht, da & T nicht auf Pin umgewandelt werden kann.

Dies ist in der Tat keine ausreichende Bedingung. Sie sind in meinem Modell gleich, weil wir in meinem früheren Beitrag die folgende Definition getroffen haben:

Definition 5: PinBox<T>.shr . Ein Zeiger ptr und eine Lebensdauer erfüllen den gemeinsamen Typstatus von PinBox<T> wenn ptr ein schreibgeschützter Zeiger auf einen anderen Zeiger inner_ptr ist, so dass T.shr('a, inner_ptr)

(Und implizit die entsprechende Definition für Pin<'a, T>.shr .)
Beachten Sie, wie PinBox<T>.shr von T.shr und sonst nichts abhängt. Dies macht PinBox<T>.shr genau die gleiche Invariante wie Box<T>.shr , was impliziert, dass &Box<T> = &PinBox<T> . Ähnliche Überlegungen zeigen, dass &&T = &Pin<T> .

Dies ist also keine Folge der im RFC niedergeschriebenen API oder des Vertrags. Es ist eine Konsequenz des Modells, das nur drei Typzustände hat: Besitz, geteilt, angeheftet. Wenn Sie gegen &&T = &Pin<T> argumentieren möchten, müssen Sie sich für die Einführung eines vierten Typzustands aussprechen, "shared pinned".

@ MicCahmer

In vielen Diskussionen um Pin scheint die Annahme zu bestehen, dass das "Projizieren" eines Pins auf ein privates Feld als sicher angesehen werden sollte. Ich denke nicht, dass das ganz stimmt.

Das ist ein sehr guter Punkt! Um ganz klar zu sein, es gibt kein Problem damit, das Feld p von Shenanigans öffentlich zu machen, oder? Zu diesem Zeitpunkt könnte jeder Client do_something_that_needs_pinning schreiben, und die Absicht des RFC ist es, dies sicher zu machen. (Ich weiß nicht, warum der RFC speziell private Felder erwähnt. Meine Interpretation war immer, dass es mit allen Feldern funktionieren sollte.)

Es ist interessant zu sehen, wie dies mit der Interpretation von drop in meinem Modell interagiert. Dort schrieb ich, dass drop(ptr) eine Voraussetzung von T.pin(ptr) . Bei dieser Interpretation wäre der eigentliche fehlerhafte Code in Ihrem Beispiel die Implementierung von drop ! (Jetzt frage ich mich, warum ich das nicht schon beim Schreiben des Beitrags bemerkt habe ...) Ich denke, wir wollen schließlich sichere Projektionen auf Felder zulassen, und wir sollten drop wirklich nicht mit &mut wenn der Typ feststeckt. Das ist eindeutig falsch.

Gibt es eine Möglichkeit, entweder (a) impl Drop unsicher zu machen, wenn der Typ !Unpin , oder (b) seine Signatur in drop(self: Pin<Self>) ändern, wenn T: !Unpin ? Beide klingen extrem weit hergeholt, aber auf der anderen Seite würden beide den Punkt von @MicahChalmer lösen und gleichzeitig die Sicherheit der Drop::drop ändern könnten, um einfach immer Pin<Self> ;) Aber zu diesem Zeitpunkt ist dies natürlich keine reine Bibliothekslösung mehr. Der traurige Teil ist, dass wir niemals sichere Feldprojektionen haben können, wenn wir uns so wie sie sind stabilisieren.

@RalfJung Ich interessiere mich also mehr für die praktischen Fragen (wie Sie wahrscheinlich erwarten würden: zwinker :). Ich denke, es gibt zwei:

  • Sollte Pin<T: !Unpin> Deref<Target =T> implementieren?
  • Sollte es sowohl ein Pin<T> als auch ein PinMut<T> (ersteres ist ein gemeinsamer Pin)?

Ich sehe keinen Grund, die erste Frage negativ zu beantworten. Ich denke, Sie haben die zweite Frage wieder geöffnet. Ich bin geneigt, auf zwei verschiedene Arten von Stiften zurückzukommen (aber nicht vollständig überzeugt). Wenn wir dies jemals auf einen Referenztyp auf Sprachebene aktualisieren, hätten wir &pin T und &pin mut T .

Unabhängig davon, was wir tun, benötigt Ihr Modell wahrscheinlich einen vierten Typstatus, um die API-Invarianten genau wiederzugeben. Ich denke nicht, dass die Umwandlung eines &&T in ein &Pin<T> sicher sein sollte.

Ich interessiere mich also mehr für die praktischen Fragen (wie Sie wahrscheinlich ein Augenzwinkern erwarten würden).

Fair genug. ;) Ich denke jedoch, dass es wichtig ist, dass wir zumindest ein grundlegendes Verständnis dafür haben, was um Pin herum garantiert ist und was nicht, sobald unsicherer Code ins Bild kommt. Basic Rust hatte mehrere Jahre Stabilisierung, um dies herauszufinden. Jetzt wiederholen wir dies für Pin in ein paar Monaten. Während Pin Bezug auf die Syntax nur für Bibliotheken verfügbar ist, ist es für das Modell eine bedeutende Ergänzung. Ich halte es für ratsam, dass wir so umfassend wie möglich dokumentieren, was unsicherer Code ist und nicht annehmen oder tun dürfen, um Pin .

Obwohl diese Bedenken derzeit theoretisch sind, werden sie plötzlich sehr praktisch sein, sobald wir die erste Unklarheit haben, weil unsicherer Code inkompatible Annahmen macht.


Zu Ihrer zweiten Frage:

Unabhängig davon, was wir tun, benötigt Ihr Modell wahrscheinlich einen vierten Typstatus, um die API-Invarianten genau wiederzugeben. Ich glaube nicht, ein && T in ein & Pin umzuwandelnsollte sicher sein.

Das habe ich erwartet.

Wenn wir konservativ sein wollen, könnten wir Pin in PinMut umbenennen, aber nicht PinShr hinzufügen (wahrscheinlich Pin aber ich versuche hier zu disambiguieren ) und erklären Sie, dass unsicherer Code davon ausgehen kann, dass ein &PinMut<T> tatsächlich auf etwas Fixiertes verweist. Dann hätten wir die Möglichkeit, später Pin als gemeinsam genutzte Referenz hinzuzufügen.

Ein praktischer Grund für PinShr wurde von @comex angesprochen : Es sollte möglich sein, einen Getter bereitzustellen, der von PinShr<'a, Struct> bis PinShr<'a, Field> ; Wenn wir &PinMut für gemeinsam genutzte Pins verwenden, funktioniert das nicht. Ich weiß nicht, wie viel solche Getter benötigt werden.


Für Ihre erste Frage scheint es einige starke Argumente zu geben: (a) die aktuellen Pläne für objektsichere beliebige Selbsttypen und (b) die sichere Verwendung der großen Anzahl vorhandener APIs für gemeinsam genutzte Referenzen, wenn a PinShr (oder PinMut ).

Es ist bedauerlich, dass wir anscheinend keine einfache Möglichkeit haben, Operationen auf ähnliche Weise bereitzustellen, die sowohl für &mut als auch für PinMut funktionieren können. Immerhin hat viel Code, der an &mut arbeitet, nicht die Absicht, mem::swap . (Dies wäre meines Erachtens der Hauptvorteil einer auf !DynSized basierenden Lösung oder einer vergleichbaren Lösung: &mut würde sich in einen Typ für Referenzen verwandeln, die möglicherweise angeheftet sind oder nicht. Wir könnte dies als einen weiteren Bibliothekstyp in der Pin API haben, aber das ist ziemlich sinnlos, da wir bereits alle diese &mut Methoden da draußen haben.)

Es gibt ein leichtes Argument dagegen, nämlich Typen, die "interessante" Dinge sowohl für &T als auch für PinMut<T> tun wollen. Das wird schwer zu machen sein, nicht alles ist möglich und man muss sehr vorsichtig sein. Aber ich denke nicht, dass dies die guten Argumente übertrifft.

In diesem Fall (dh mit diesem impl Deref ) sollte PinShr<'a, T> mit einer sicheren Methode geliefert werden, die daraus ein &'a T (wodurch die Lebensdauer erhalten bleibt).


Und ich denke, wir müssen noch ein weiteres Problem lösen: Die Regeln für Pin::map und / oder drop im Lichte dessen, was @MicahChalmer oben bemerkt hat. Wir haben zwei Möglichkeiten:

  • Erklären Sie, dass die Verwendung von Pin::map mit einer Projektion auf ein öffentliches Feld (keine Derefs, nicht einmal implizit) immer sicher ist. Dies ist meine Interpretation des aktuellen RFC. Dies würde mit &mut und & übereinstimmen. Dann haben wir jedoch ein Problem um Drop : Wir können jetzt sicheren Code schreiben, der UB bei einem ansonsten wohlgeformten !Unpin -Typ verursacht.
  • Mach nichts Lustiges um Drop . Dann müssen wir Pin::map laute Warnungen hinzufügen, dass selbst die Verwendung für öffentliche Felder zu Unklarheiten führen kann. Selbst wenn wir jemals &pin T , können wir damit nicht auf ein Feld in sicherem Code zugreifen.

Das einzig mögliche Argument für die zweite Option, die ich sehen kann, ist, dass es möglicherweise die einzige ist, die wir tatsächlich implementieren können. ;) Ich denke, es ist in jeder Hinsicht minderwertig - es macht &pin ziemlich seltsam und unergonomisch, selbst wenn es eines Tages eingebaut wird, es ist eine Fußwaffe, es behindert die Komposition.

Es kann ein Weg sein , um die erste Option zu erreichen, aber es ist nicht trivial , und ich habe keine Ahnung , wie man es machen rückwärtskompatibel: Wir könnten ein hinzufügen Unpin gebunden Drop , und fügen Sie ein DropPinned , die nicht gebunden sind und bei denen drop Pin<Self> . Vielleicht die Unpin gebunden auf Drop in eine seltsame Art und Weise durchgesetzt werden können , wo Sie die schreiben impl Drop for S , aber dies fügt eine implizite gebunden S sagen , dass es muss Unpin . Wahrscheinlich nicht realistisch. : / (Ich denke, dies ist auch ein Punkt, an dem der auf !DynSized basierende Ansatz besser funktioniert - er verwandelt &mut T in "kann oder kann nicht fixiert werden" und behält drop Klang.)

@RalfJung @MicahChalmer Ich denke, es ist besser, nur zu dokumentieren, dass es nicht sinnvoll ist, auf einen Pin dieses Feldes zu projizieren, wenn Sie das Feld im Drop impl verlassen.

In der Tat ist es bereits heute der Fall, dass Sie (mit unsicherem Code) aus einem Feld vom Typ !Unpin herauskommen könnten, und dies ist sicher und gut definiert, solange Sie niemals auf einen Pin dieses Feldes anderswo projizieren . Der einzige Unterschied zu Drop besteht darin, dass der Auszugsteil nur sicheren Code enthält. Es scheint mir, dass sich die Notizen zu Pin::map ändern müssen, um zu beachten, dass es nicht sicher ist, wenn Sie jemals aus diesem Feld herauskommen, unabhängig vom Drop-Impl.

In einigen Fällen muss es sicher sein, die Felder eines !Unpin -Typs zu verlassen, da die Generatoren bei ihrer Rückkehr sehr wahrscheinlich eines ihrer Felder verlassen.

Ich denke, es ist besser, nur zu dokumentieren, dass es nicht sinnvoll ist, auf einen Pin dieses Feldes zu projizieren, wenn Sie das Feld im Drop-Impl verlassen.

Dies ist dann die zweite Option, die &pin für ein Feld dauerhaft zu einer unsicheren Operation macht.
Ich denke, das ist keine kleine Änderung. Dies ändert grundlegend, was pub auf einem Feld bedeutet. Wenn ich einen Bibliothekstyp verwende, kann ich nicht wissen, was er in seinem Drop-Impl tut, daher habe ich grundsätzlich keine Möglichkeit, einen angehefteten Verweis auf dieses Feld zu erhalten.

Zum Beispiel würde ich nicht einmal von Pin<Option<T>> zu Option<Pin<T>> wechseln dürfen, es sei denn, Option ausdrücklich an, dass niemals ein Drop irgendetwas tun wird " lustig". Der Compiler kann diese Anweisung nicht verstehen. Obwohl Option eine geeignete Methode dafür bereitstellen könnte, muss das Gleiche mit einem match eine unsichere Operation bleiben.

Der einzige Unterschied zu Drop besteht darin, dass der Auszugsteil nur sicheren Code enthält.

Aber das ist ein großer Unterschied, nicht wahr? Wir können willkürliche Regeln festlegen, was unsicherer Code tun kann oder nicht, aber nicht für sicheren Code.

In einigen Fällen muss es sicher sein, die Felder eines! Unpin-Typs zu verlassen, da die Generatoren bei ihrer Rückkehr sehr wahrscheinlich eines ihrer Felder verlassen.

Ich nehme an, in diesem Fall ist das Feld Unpin ? Wir könnten also wahrscheinlich eine Regel haben, die besagt, dass Pin::mut für ein öffentliches Feld einer fremden Struktur in Ordnung ist, wenn dieses Feld den Typ Unpin . Ich bin mir nicht sicher, wie nützlich das ist, aber es ist wahrscheinlich besser als nichts.

Ich möchte schnell meine Verwirrung über &Pin<T> wiederholen, die nicht mehr Garantien als &&T bietet. & , &mut und &pin bieten jeweils "gemeinsamen Zugriff", "eindeutigen Zugriff" und "eindeutigen Zugriff auf einen Wert, der nicht verschoben wird". Wenn Sie &&pin als "gemeinsamen Zugriff auf einen eindeutigen Zugriff auf einen Typ, der nicht verschoben wird" verstehen, werden Sie darauf hingewiesen, dass der Speicher gemeinsam genutzt wird (die Eindeutigkeitsgarantie von &pin wird durch die Freigabe von aufgehoben & ), aber Sie behalten trotzdem die Eigenschaft, dass der Typ nicht verschoben wird, nein?

Ich bin mir nicht sicher, was Sie fragen oder sagen. Sind Sie verwirrt, warum ich denke, dass "Shared Pinned" ein grundlegender Modus / Typestate für sich ist?

Der Punkt ist, "Shared Access" ist keine Sache, die ich selbst definieren kann. Es gibt viele verschiedene Möglichkeiten, die Freigabe zu teilen und zu koordinieren, wie die sehr unterschiedlichen Möglichkeiten zeigen, z. B. Cell , RefCell und Mutex .

Sie können nicht einfach sagen, dass Sie etwas teilen ("Aufheben der Eindeutigkeitsgarantie" von etwas), das Sie besitzen, und erwarten, dass diese Aussage Sinn macht. Sie müssen sagen, wie Sie teilen und wie Sie sicherstellen, dass dies kein Chaos verursachen kann. Sie können "teilen, indem Sie es schreibgeschützt machen" oder "teilen, indem Sie nur atomaren Zugriff über synchronisierte Ladevorgänge / Speicher gewähren" oder "teilen [innerhalb nur eines Threads], indem Sie dieses Ausleihflag koordinieren lassen, welche Art von Zugriff ausgegeben werden soll". . Einer der wichtigsten Punkte in RustBelt war die Erkenntnis, wie wichtig es ist, dass jeder Typ für sich selbst definiert, was passiert, wenn er geteilt wird.

Ich kann mir keinen Weg vorstellen, wie "Shared Pinning" als orthogonale Zusammensetzung von Sharing und Pinning entstehen kann. Vielleicht gibt es eine Möglichkeit, den Begriff eines "Freigabemechanismus" zu definieren, der dann entweder auf die eigene oder die festgesteckte Invariante angewendet werden könnte, um "(normal) gemeinsam" und "festgesteckt geteilt" hervorzurufen, aber ich bezweifle dies ernsthaft. Wie wir gesehen haben, fällt das für RefCell flach - wenn RefCell für die gemeinsam genutzte festgesteckte Invariante etwas Ähnliches tut wie für die gerade gemeinsam genutzte Invariante, können wir dies mit & &pin RefCell<T> sicherlich nicht rechtfertigen &RefCell<T> (mit Deref ) über borrow_mut wir eine &mut Referenz erhalten, die besagt, dass kein Pinning stattgefunden hat.

@ RalfJung

Wir könnten einen Unpin hinzufügen, der an Drop gebunden ist, und einen DropPinned hinzufügen, der nicht gebunden ist und bei dem Drop Pin nimmt.

Ist die Definition von Drop wirklich das Problem? Eine andere Möglichkeit, darüber nachzudenken, besteht darin, mem::swap und mem::replace die Schuld zu geben. Mit diesen Vorgängen können Sie etwas verschieben, das Sie nicht besitzen. Angenommen, _them_ wurde eine T: Unpin -Bindung hinzugefügt?

Für den Anfang würde dies das drop Loch beheben, auf das ich hingewiesen habe - mein Shenanigans würde nicht kompiliert werden können, und ich glaube nicht, dass ich es schaffen könnte, die Pin-Versprechen ohne ein weiteres unsafe zu verletzen &mut Verweis auf einen zuvor angehefteten Wert in drop , warum sollten Sie ihn auf nur drop ?

Wenn ich nichts vermisse, denke ich, dass dies es sicher machen würde, jederzeit eine &mut Referenz von einem PinMut<T> auszuleihen. (Ich verwende PinMut , um auf das zu verweisen, was in der aktuellen Nacht als Pin wird, um Verwechslungen mit der Diskussion über gemeinsam genutzte Pins zu vermeiden.) PinMut<T> könnte DerefMut implementieren T: Unpin .

Es ist bedauerlich, dass wir anscheinend keine einfache Möglichkeit haben, Operationen bereitzustellen, die sowohl für & mut als auch für PinMut; Immerhin hat viel Code, der an & mut arbeitet, nicht die Absicht, mem::swap .

Ein DerefMut Impl auf PinMut würde das beheben, oder? Code, der sich um das Fixieren kümmert und PinMut erfordert, kann Code aufrufen, der sicher und einfach mit &mut funktioniert. Die Last würde stattdessen auf generische Funktionen gelegt, die _do_ mem::swap möchten - diese müssten mit einer Bindung von Unpin versehen werden, oder sie müssten unsafe und aufpassen die Pin-Bedingungen nicht zu verletzen.

Das Hinzufügen solcher Grenzen zu swap und replace würde jetzt die Abwärtskompatibilität beeinträchtigen, die bis zur ersten stabilen Version zurückreicht. Ich sehe keinen realistischen Weg, um von hier dorthin zu gelangen. Aber vermisse ich ein anderes Loch in dem Gedanken, dass es das Richtige gewesen wäre, wenn dies nur in den Tagen vor 1.0 bekannt gewesen wäre?

So wie es ist, sehe ich keine bessere Lösung als das, was @withoutboats gesagt hat - halten Sie map unsicher und fügen Sie eine Nachricht in die Dokumente ein, in der die Leute gewarnt werden, kein Feld zu verlassen, das zuvor in einem drop fixiert war

Wir können willkürliche Regeln festlegen, was unsicherer Code tun kann oder nicht, aber nicht für sicheren Code.

Die Verwendung von unsafe legt immer Regeln für den umgebenden sicheren Code fest. Die gute Nachricht hier ist, dass, soweit wir wissen, wenn eine feststeckbare Struktur eine Methode zum Projizieren eines Pins auf ein privates Feld hat, nur ein eigenes drop impl dies verwenden kann, um den Vertrag in sicherem Code zu verletzen. Es ist also immer noch möglich, eine solche Projektion hinzuzufügen und _users_ dieser Struktur mit einer vollständig sicheren API zu präsentieren.

Ist die Definition von Drop hier wirklich das Problem?

Das Zuweisen von Schuld ist etwas willkürlich, es gibt verschiedene Dinge, die man ändern könnte, um das Soliditätsloch zu schließen. Aber sind wir uns einig, dass eine Änderung von Drop wie ich vorgeschlagen habe, das Problem beheben würde?

Eine andere Möglichkeit, darüber nachzudenken, besteht darin, mem :: swap und mem :: replace die Schuld zu geben. Mit diesen Vorgängen können Sie etwas verschieben, das Sie nicht besitzen. Angenommen, ihnen wurde ein T: Unpin gebunden hinzugefügt?

Nun, das würde die Verwendung von &mut T für !Unpin Typen im Allgemeinen sicher machen. Wie Sie bemerkt haben, würden wir nicht einmal mehr PinMut brauchen. PinMut<'a, T> in Ihrem Vorschlag könnte als &'a mut T , oder?
Dies ist im Wesentlichen der ?Move -Vorschlag, der zuvor aufgrund von Abwärtskompatibilitäts- und Sprachkomplexitätsproblemen verworfen wurde.

Die Verwendung von unsicheren Regeln legt immer Regeln für den umgebenden Sicherheitscode fest.

Ich bin mir nicht sicher, was du meinst. Über die Datenschutzgrenze hinaus darf dies nicht der Fall sein. Unsicherer Code kann seinen Clients nichts aufzwingen.

Die gute Nachricht hier ist, dass, soweit wir wissen, wenn eine feststeckbare Struktur eine Methode zum Projizieren eines Pins auf ein privates Feld hat, nur ein eigenes Drop-Impl dies verwenden kann, um den Vertrag in sicherem Code zu verletzen. Es ist also weiterhin möglich, eine solche Projektion hinzuzufügen und Benutzern dieser Struktur eine vollständig sichere API zu präsentieren.

Ja, Typen können sich anmelden, um die Projektion für sicher zu erklären. Aber z. B. wird der Leihprüfer nicht verstehen, dass dies ein Feldzugriff ist. Wenn Sie also PinMut<Struct> angeben, können Sie solche Methoden nicht verwenden, um PinMut auf zwei verschiedene Felder zu erhalten.

Aber sind wir uns einig, dass eine Änderung von Drop, wie ich vorgeschlagen habe, das Problem beheben würde?

Ich stimme zu, das würde es beheben.

Wir würden PinMut nicht einmal mehr brauchen. PinMut <'a, T> in Ihrem Vorschlag könnte als &' a mut T definiert werden, oder?

Nein, PinMut<'a, T> noch versprechen, dass sich der Referent nie wieder bewegen wird. Mit &'a mut T konnte man nur darauf vertrauen, dass es sich nicht lebenslang bewegt 'a . Dies wäre nach wie vor zulässig:

`` `` Rost
Struktur X;
impl! Unpin für X {}
fn nimmt_a_mut_ref (_: & mut X) {}

fn geliehen_und_ bewegen_und_borrow_again () {
sei mut x = X;
nimmt_a_mut_ref (& mut x);
let mut b = Box :: new (x);
nimmt_a_mut_ref (& mut * b);
}}
`` ``

Es wäre sicher, von PinMut<'a, T> auf &'a mut T aber nicht umgekehrt - PinMut::new_unchecked würde immer noch existieren und immer noch unsafe .

Dies ist im Wesentlichen der ?Move -Vorschlag, der zuvor aufgrund von Abwärtskompatibilitäts- und Sprachkomplexitätsproblemen verworfen wurde.

So wie ich es verstehe, haben die ?Move -Vorschläge versucht, nicht mehr PinMut zu benötigen, indem sie die grundlegenden Sprachregeln geändert haben, um das obige Code-Snippet zu verbieten (indem sie Unpin sei ein Move Merkmal.) Ich schlage so etwas nicht vor - mein Vorschlag ist, genau so zu beginnen, wie es jetzt in der Nacht ist, plus:

  • Fügen Sie ein Unpin , das an die Funktionen std::mem::swap und std::mem::replace gebunden ist
  • Entfernen Sie die Unpin Bindung von der DerefMut Impl von Pin ( PinMut wenn diese Umbenennung erfolgt)

Das ist alles - keine grundlegenden sprachlichen Änderungen an der Funktionsweise von Bewegungen oder Ähnlichem. Meine Behauptung lautet: Ja, dies wäre eine bahnbrechende Änderung, aber sie hätte weniger bahnbrechende Auswirkungen als die Änderung von Drop (was wiederum immer noch weniger drastisch ist als die ?Move Vorschläge), während viele beibehalten werden der Vorteile. Insbesondere würde es eine sichere Pin-Projektion ermöglichen (zumindest für private Felder, und ich denke sogar für öffentliche? Nicht ganz sicher) und Situationen verhindern, in denen paralleler Code geschrieben werden muss, um mit PinMut und &mut .

Nein, PinMut <'a, T> muss weiterhin versprechen, dass sich der Referent nie wieder bewegen wird. Mit & 'a mut T konnte man nur darauf vertrauen, dass es sich nicht lebenslang bewegt' a.

Aha. Macht Sinn.

Soweit ich weiß, haben die? Move-Vorschläge versucht, PinMut nicht mehr zu benötigen, indem sie die grundlegenden Sprachregeln geändert haben, um das obige Code-Snippet zu verbieten (indem Unpin zu einem Move-Merkmal gemacht wurde).

Verstanden.

Meine Behauptung lautet: Ja, dies wäre eine bahnbrechende Änderung, aber sie hätte weniger brechende Auswirkungen als eine Änderung von Drop

Welche Änderung sollst du fallen lassen? Hinzufügen einer Unpin -Bindung? Sie mögen Recht haben, aber ich habe keine Ahnung, wie weit verbreitet mem::swap und mem::replace sind.

Insbesondere würde es eine sichere Stiftprojektion ermöglichen (zumindest für private Felder, und ich denke sogar für die Öffentlichkeit? Nicht ganz sicher)

Ich sehe nicht ein, wie privat oder öffentlich hier überhaupt etwas bewirken kann. Wie würde ein öffentliches Feld weniger zulassen als ein privates Feld?

Aber ja, das scheint insgesamt zusammenzuhalten. Future würde immer noch PinMut da es sich auf Dinge verlassen muss, die sich nie bewegen, aber es würde eine größere Auswahl an Methoden zur Verfügung stehen.

Der Kompatibilitätsaspekt ist jedoch groß. Ich denke nicht, dass dies realistisch ist, es würde den gesamten generischen Code brechen, der mem::swap / mem::replace aufruft. Außerdem kann derzeit unsicherer Code diese Methoden mithilfe von ptr::read / ptr::write selbst implementieren. Dies könnte zu einem stillen Bruch des vorhandenen unsicheren Codes führen. Das ist ein No-Go, denke ich.

Während wir uns mit der Einführung von Unpin befassen, die an mem::swap und mem::replace gebunden sind (und sich nicht um Bruch sorgen). Wenn wir davon ausgehen, dass die Route "Compiler eingebaut" verwendet wird. Wäre es möglich, die gleiche Grenze für mem::forget einzuführen, um sicherzustellen, dass Destruktoren für stapelgesteckte Variablen ausgeführt werden, die thread::scoped klingen lassen, und in bestimmten Fällen zu vermeiden, dass Sie Ihre Hosen vorab kacken?

Beachten Sie, dass mem::forget auf einem PinBox weiterhin zulässig ist. Die neue vorgeschlagene Garantie in Bezug auf Drop lautet NICHT "Dinge lecken nicht". Es heißt "Dinge werden nicht freigegeben, ohne dass drop zuerst aufgerufen wird". Das ist eine ganz andere Aussage. Diese Garantie hilft thread::scoped .

Um Kontext hinzuzufügen, verschiebe ich häufig Daten aus einer Struktur, die Future implementiert. Es kommt häufig vor, dass Aufräumarbeiten durchgeführt werden müssen, wenn eine Zukunft nie vollständig abgefragt wurde (vor Abschluss der Abfrage gelöscht).

Ich hätte diese Landmine also mit Sicherheit getroffen, wenn ich vorhandenen Code auf Futures 0.3 portiert hätte, selbst wenn die Dokumentation zu map hinzugefügt worden wäre.

@carllerche Die Funktion map sagt ganz klar, dass Sie dies nicht verwenden dürfen, um etwas zu verschieben. Wir können und wollen uns nicht vor Personen schützen, die absichtlich aus einem Pin<T> ausziehen, aber Sie müssen sich aus dem Weg räumen (mit unsicherem Code), um dies zu tun. Ich würde das nicht Landmine nennen.

Also, auf welche Landmine beziehen Sie sich?

@RalfJung Ich habe versucht, die Grenzen für die Zuordnung von angehefteten Referenzen selbst herauszufinden, und ich denke, dass dies eine riesige Fußwaffe sein wird, wenn sie nicht bald gelöst wird. Ich denke, die erste Lösung ist trotz der Komplexität vorzuziehen. Wenn Verbraucher nicht sicher auf angeheftete Felder projizieren können, ist es für Verbraucher praktisch unmöglich, APIs zu verwenden, die auf dem Anheften basieren, ohne unsicheren Code zu schreiben.

Wenn dies nicht möglich ist, müssen in der Praxis die meisten verwendbaren APIs, die Pinning verwenden, PinShare verwenden. Das ist vielleicht kein so großes Handicap, denke ich, aber ich bin mir immer noch nicht sicher, in welcher Beziehung Unpin steht. Im Einzelnen: Nehmen wir an, ich greife nach einer Pinshared-Referenz und erhalte eine Referenz auf ein Feld des Typs (für eine bestimmte Lebensdauer). Kann ich mich wirklich darauf verlassen, dass es sich nicht bewegt, wenn das Leben vorbei ist? Ich kann es wahrscheinlich, wenn das Feld !Unpin also ist es vielleicht in Ordnung, solange Pin nicht herausragen kann - ich mache mir hauptsächlich Sorgen um Aufzählungen. Es sei denn, Sie sagen, dass selbst das gemeinsame Fixieren nicht sicher sein kann, ohne Drop zu reparieren. In diesem Fall denke ich, dass das Fixieren von Drop für die Arbeit mit Pinning im Wesentlichen erfolgen muss . Andernfalls wird es zu einer Nischenbibliotheksfunktion, die nicht wirklich sicher verwendet werden kann und (IMO) keinen Ehrenplatz in der Kernsprache verdient, selbst wenn sie für Futures sehr nützlich ist.

Ich sollte auch erwähnen, dass die einzige praktische API, die ich bisher für aufdringliche Sammlungen habe (ich muss noch die Knicke herausarbeiten), noch stärkere Garantien als diese benötigt; es muss in der Lage sein , diesen Rückgang zu gewährleisten ist nicht beschränkt, solange es eine borrow in die Sammlung genannt. Ich kann dies mit einer GhostCell-ähnlichen Technik tun, aber es fühlt sich sehr umständlich an und erfordert, dass der Benutzer eine manuelle Speicherverwaltung durchführt (da wir auslaufen müssen, wenn der Sicherungsspeicher für etwas in der Sammlung gelöscht wird, ohne dass das Token bereitgestellt wird). Daher mache ich mir ein bisschen Sorgen, dass das automatische Löschen selbst schwierig zu sein scheint, wenn Typen mit Pinning auf interessante Weise verwendet werden.

Aus Neugier: Was sind die Argumente gegen das Hinzufügen eines Unpin , das an Drop gebunden ist? Sie haben die Abwärtskompatibilität angeführt, mit der Alternative, dass Sie das zu löschende Objekt irgendwie automatisch binden müssten, aber bereits seltsame Einschränkungen auf Systemebene löschen, die für andere Merkmale nicht existieren - warum ist diese so unterschiedlich? Es ist sicherlich nicht so elegant wie nur Drop Pin<T> aber wir können diese Änderung derzeit noch nicht vornehmen. Ist das Problem , dass wir nicht wirklich wissen , was zu tun ist, wenn Sie auf einer Art Call - Drop zu tun , dass nur eine hat Unpin Implementierung, wenn der Typ selbst ist !Unpin ? Ich würde vermuten, dass das Auslösen einer Ausnahme in der Drop-Implementierung in diesem Fall der richtige Ansatz ist, da jeder, der sich darauf verlässt, dass drop für generische Typen ausgeführt wird, den Panikfall bereits behandeln muss. Das würde bedeuten, dass es in der Praxis sehr schwierig wäre, !Unpin -Typen zu verwenden, ohne dass eine Gruppe von Menschen ihren Code aktualisiert, um das neue Merkmal Drop (sodass das Ökosystem gezwungen wäre, alles zu verschieben thew new version), aber ich denke, dass ich damit einverstanden wäre, da es immer noch die Solidität bewahren und keinen Code brechen würde, der überhaupt nicht !Unpin . Außerdem würde die Sache "Ihr Code gerät in Panik, wenn die Bibliothek nicht aktualisiert wird" die Leute wirklich dazu anregen, weiterzumachen!

In der Tat ist hier mein vorgeschlagenes Design:

Erweitern Sie das Drop-Merkmal mit einer zweiten Methode, die Pin verwendet, wie Sie vorgeschlagen haben. Machen Sie Drop (ich nehme an, ,? Drop immer besondere Gefasste werden kann; plus, Drop -Funktionen werden normalerweise automatisch generiert. Jetzt haben Sie genau das gleiche Verhalten, das ich oben vorgeschlagen habe, ohne Abwärtskompatibilitätsprobleme und ohne den Versuch, eine Grenze umständlich automatisch abzuleiten.

Es hat das Problem, dass Bibliotheken von Drittanbietern aktualisiert werden müssen, damit sie mit !Unpin -Typen praktisch nützlich sind, aber wie gesagt, das ist wohl eine gute Sache. Auf jeden Fall habe ich keine Ahnung, wie viele drop -Implementierungen ihre Felder tatsächlich so mutieren, dass &mut erforderlich sind - die meisten, von denen ich mir vorstellen kann, dass sie etwas Interessantes tun, verwenden entweder unsafe oder verwenden Sie innere Veränderlichkeit, aber ich denke wahrscheinlich nicht an einige häufige Verwendungszwecke.

Die Hauptsache bei diesem Ansatz ist, dass er genommen werden müsste, bevor Pin stabilisiert wurde. Dies ist ein weiterer Grund, warum ich wirklich hoffe, dass Pin nicht überstürzt wird. Ich denke, wir haben die Konsequenzen für das Design nicht ausreichend untersucht.

(Ich sehe noch ein potenzielles Problem: ManuallyDrop und Gewerkschaften im Allgemeinen bedeuten, dass jemand wahrscheinlich einen Destruktor über einen generischen Typ geschrieben haben könnte, der davon ausgegangen ist, dass die darin enthaltene Implementierung von drop nicht in Panik geraten kann , einfach weil es nie ausgeführt werden konnte (es durfte auch keine anderen &mut -Funktionen auf dem generischen T aufrufen, außer solchen, die auf unsicheren Merkmalen implementiert waren, die versprachen, nicht in Panik zu geraten). Seit Pin Wenn ein Typ bereits garantiert, dass seine Drop-Implementierung ausgeführt wird, bevor sein Speicher zerstört wird [gemäß der neuen Semantik], glaube ich, dass der Typ !Unpin innerhalb eines für diesen Zweck verwendeten ManuallyDrop in vorhandenem Code konnte überhaupt nicht als fixiert betrachtet werden, daher sollte der vorhandene Code immer noch korrekt sein, wenn diese Annahme getroffen wird. Natürlich sollten Sie nicht in der Lage sein, einen Pin sicher in ein ManuallyDrop zu projizieren, da Es kann nur sicher sein, wenn Sie garantieren, dass sein Destruktor vor dem Löschen aufgerufen wird. Ich weiß nur nicht, wie man diesen Fall dem Comp mitteilen würde Kann dies das "Augenklappen" -Stoff überhaupt ausnutzen, da es anscheinend für einen ähnlichen Zweck gedacht ist?]. Ich bin mir nicht sicher, ob das semantisch funktioniert, aber es funktioniert wahrscheinlich für die Zwecke der Implementierung von Drop für vorhandenen Code.

Zum Thema Augenklappe ... Ich bin mir immer noch nicht sicher, welche genaue formale Definition sie hat, aber wenn die allgemeine Idee ist, dass für T keine interessanten generischen Funktionen aufgerufen werden, könnte man das vielleicht weiter ausnutzen? Das heißt, wenn die Implementierung von drop_pinned für den Typ !Unpin implementiert würde, würde sich der Container, der die Augenklappe respektiert, korrekt verhalten. Es scheint mir möglich , dass man dann einen Zeitfehler für !Unpin -Typen kompilieren könnte, die Drop implementieren, drop_pinned nicht implementieren und ihre generischen Parameter nicht in Eyepatch setzen Genauso wie wir es tun, wenn Sie Behälter ohne Augenklappe mit selbstreferenzieller Lebensdauer verwenden.

Existentials stellen jedoch ein ernstes Abwärtskompatibilitätsrisiko für jede Kompilierungszeitstrategie dar. Aus diesem Grund halte ich eine Lösung, die zur Laufzeit fehlschlägt, für realistischer. Dies hätte keinen Laufzeitfehler und keine Fehlalarme.

Bearbeiten: Eigentlich kratzen Sie alle oben genannten: Wir müssen uns nur wirklich um zugängliche Felder in Destruktoren kümmern, nicht um &mut Zugriff im Allgemeinen, richtig? Weil wir uns nur Sorgen um implizite Feldprojektionen von &mut self .

Ich erfinde vielleicht nur den !DynSized -Vorschlag neu, aber im Wesentlichen: Generische Container müssen bereits selbst !Unpin wenn sie Methoden offenlegen, die sich um den Pin-Typestate kümmern (mir ist klar, dass dies verdächtig nach falscher Parametrizität klingt Argument, aber hör mir zu!). Existentials wie Box<Trait> und &mut Trait spiegeln nicht unbedingt ihren Unpin -Status wider, aber (zumindest, wenn man auf die aktuellen APIs starrt?) Ich glaube nicht, dass Pin<Box<T>> und Pin<&mut T> müssen unbedingt zu Pin<T> gezwungen werden, wobei T: !Unpin ; Wenn dies nicht implementiert wird, bedeutet dies, dass angeheftete Verweise auf diese Typen keinen sicheren Zugriff auf ihr Inneres bieten (beachten Sie, dass es dafür einen Präzedenzfall mit der Beziehung von & mut gibt und &: & mut & T nicht mit & mut T erzwungen werden kann, sondern nur mit & T und Box <& mut T> kann nicht mit Box<T> erzwungen werden, sondern nur, wenn verschiedene Typzustände kollidieren, müssen sie im Allgemeinen nicht automatisch weitergegeben werden. Ich erkenne, dass normalerweise &mut T , Box<T> und T aus typografischer Sicht als vollständig austauschbar angesehen werden, und dieses Argument erstreckt sich nicht auf hypothetische Inline-Existentiale, aber vielleicht ist dies der Fall die Substanz des DynSize -Vorschlags (erlauben Sie keine sicheren Swaps oder mem::replace s für Merkmalsobjektwerte, denke ich? Eigentlich ist es für sie bereits verboten ... aber ich gehe davon aus, dass es solche gibt einen Grund, warum sich dies in Zukunft ändern könnte). Das macht eine Lösung zur Kompilierungszeit sehr einfach: für jede Struktur, die (transitiv) direkt besitzt (no & mut, &, Box oder rohe Zeiger - von denen keiner den Zugriff auf Pin sicher transitiv verbreiten würde , mit Ausnahme von & für gemeinsam genutzte Pins, aber & können ohnehin nicht entfernt werden - oder wenn Sie sich für die Lösung "Objekte mit Merkmalen können nicht verschoben werden" entschieden haben, Sie könnte auch Felder transitiv entlang &mut und Box überprüfen (glaube ich) und hat (im Sinne der Sichtbarkeit) Zugriff auf einen bekannten !Unpin -Typ (einschließlich sich selbst), müsste es Implementieren Sie die zweite Art von drop . Das scheint mir völlig in Ordnung zu sein und überhaupt keine abwärtskompatible Fußwaffe, da keine stabilen Typen !Unpin Leute müssen möglicherweise Destruktoren nach dem Aktualisieren einer Bibliothek neu implementieren, aber das ist alles, richtig? Darüber hinaus würden interne Implementierungsdetails intern bleiben. Nur wenn ein !Unpin -Feld angezeigt würde, gäbe es Probleme. Schließlich würden alle generischen Containertypen (nicht nur Vec und Standardbibliotheksmaterial, sondern im Wesentlichen das gesamte crates.io-Ökosystem) weiterhin einwandfrei funktionieren. Was ist das Katastrophale an dieser Lösung?

(Tatsächlich scheint es mir, dass Sie, selbst wenn Sie dies zum Definitionszeitpunkt von drop nicht erzwingen könnten, dies zumindest zum Zeitpunkt der Typinstanziierung tun sollten, wie dies bei dropck Fall ist. da Sie sich nur um vollständig instanziierte Typen kümmern müssen).

Erneutes Lesen des Vorschlags Dynsized : Ich stelle fest, dass ein Argument dagegen vorsah, dass unbewegliche Typen immer DynSized noch bevor sie angeheftet werden. Ich argumentiere oben, dass wir uns darüber wirklich nur im Fall von Merkmalsobjekten Sorgen machen müssen; Es könnte möglich (obwohl schwer zu rechtfertigen) sein, das Erzwingen eines !Unpin -Typs zu einem Merkmal durchzusetzen, das eine explizite Begrenzung mit + ?DynSized erfordert (oder was auch immer; es könnte automatisch erfolgen, nehme ich an). Es kann zwar viele Fälle geben, in denen die Größe für !Unpin -Typen bekannt sein muss (tatsächlich habe ich einen solchen Anwendungsfall!), Oder sie müssen ausgetauscht werden können, bevor sie fixiert werden, aber ich hoffe, dass dies der Fall ist Sehr wenige solcher Fälle für das Interieur von Merkmalsobjekten aus unbeweglichen Typen (die einzigen Fälle, die wir jetzt haben, Box -> Rc , die wir explizit verbieten möchten, oder? Es ist mir eigentlich nicht einmal klar, dass das size_of_val hier wirklich ein Problem ist oder nicht, da ich immer noch nicht weiß, ob von Ihnen erwartet wird, dass Sie Pin<'a, Box<Trait> in Pin<'a, Trait> verwandeln können Sized . Man möchte auf jeden Fall in der Lage sein, sie mit !Unpin zu binden, aber wie gesagt, ich denke, die Leute wollen vermeiden, mehr negative Eigenschaften hinzuzufügen, als wir bereits brauchen (persönlich hoffe ich, dass !Unpin Merkmalsobjekte werden so selten und spezialisiert sein, dass das Begrenzen von Merkmalsobjekten mit ?Unpin anstelle von Unpin völlig vernünftig wäre und das Typensystem nicht zu stark infiziert; die meisten Verwendungen für !Unpin Ich kann mir vorstellen, dass es für die meisten vorhandenen Merkmale keine nützlichen Implementierungen gibt, da sie im Allgemeinen Aktionen für Pin<self> ausführen möchten. Normalerweise möchten Sie sie auch mit PinBox oder PinTypedArena oder was auch immer, an welchem ​​Punkt ?Unpin Grenzen ziemlich natürlich aussehen).

Ich habe ein neues Design, das meiner Meinung nach nicht annähernd so schrecklich ist wie das alte: https://github.com/pythonesque/pintrusive/blob/master/src/main.rs. Anstatt zu versuchen, Pin-Projektionen überall zum Laufen zu bringen, fragt dieses Design, wie wir mit "altem" Rust-Code zusammenarbeiten können, der nichts über Pinning weiß, von "modernem" Rust-Code, der immer das Pinning unterstützen möchte. Die offensichtliche Antwort scheint darin zu bestehen, das vorgeschlagene Merkmal PinDrop @RalfJung zu verwenden , jedoch nur für Typen, die beide einen benutzerdefinierten Drop haben und Felder projizieren möchten.

Typen entscheiden sich explizit für die Projektion von Feldern (entsprechend der Ableitung des Merkmals PinFields ), was dem in "modernem" Rust geschriebenen Code entspricht. Es werden jedoch keine zusätzlichen Anforderungen an Code in "Legacy" Rust gestellt, sondern es wird nur die Projektion auf Tiefe 1 für eine bestimmte Ableitung von PinFields . Es wird auch nicht versucht, Pin durch Referenzen zu verschieben, was meiner Meinung nach wahrscheinlich eine gute Idee ist, dies sowieso nicht zu tun. Es unterstützt Strukturen und Aufzählungen, einschließlich jeglicher Disjunktheitsanalyse, die Rust bereitstellen sollte, indem eine Struktur mit identischen Feldern und Varianten generiert wird, wobei jedoch die Typen durch Pin 'd Verweise auf die Typen ersetzt werden (dies sollte trivial sein um dies auf Pin und PinMut wenn diese Änderung vorgenommen wird). Offensichtlich ist dies nicht ideal (obwohl das Optimierungsprogramm hoffentlich das meiste davon loswerden kann), aber es hat den Vorteil, dass es mit Borrowck und NLL funktioniert und leicht mit Aufzählungen (im Gegensatz zu generierten Accessoren).

Das Sicherheitsargument stellt sicher, dass entweder Drop überhaupt nicht für die Struktur implementiert ist oder dass Drop eine einfache Implementierung ist, bei der nur PinDrop aufgerufen wird (eine Version von Drop, die Pin verwendet)). Ich glaube, dies schließt alle Probleme mit der Solidität beim Projizieren von angehefteten Feldern mit einem Fragezeichen aus: Mein Hauptanliegen ist es, ein gutes Argument dafür zu finden, warum disjunkte Felder auf genau demselben Container (dh Felder in Tiefe 1) sich gegenseitig ungültig machen könnten Stifte in ihren Destruktoren wären ohne Stiftvorsprünge bereits ungültig. Ich glaube , ich kann dies rechtfertigen , wenn wir nachweisen können , dass Sie auch das gleiche tun können , wenn sie in separaten PinBoxes gehalten wurden, was bedeutet , dass dort ,

@pythonesque Ich habe nicht wirklich gefolgt, was Sie oben über DynSize , aber ich nehme an, dass jetzt sowieso alles veraltet ist? Ich werde also nur Ihren letzten Beitrag kommentieren.

Zusammenfassend sagen Sie, dass das Projizieren in ein Feld einer Struktur / Aufzählung (einschließlich pub -Feldern) im Allgemeinen unsicher ist, ein Typ sich jedoch für sichere Feldprojektionen entscheiden kann, indem er Drop nicht implementiert. . Wenn der Typ einen Destruktor möchte, muss er PinDrop anstelle von Drop :

trait PinDrop {
  fn pin_drop(self: PinMut<Self>);
}

Wir haben bereits eine typecheck Suche nach Drop , um das Verlassen eines Feldes abzulehnen. Es erscheint daher nicht unrealistisch, auch nach Drop zu suchen, um das Projizieren durch ein &pin abzulehnen. Natürlich würde die Prüfung "Aus dem Feld ziehen" immer noch ablehnen, wenn es ein PinDrop , während die Projektion in diesem Fall erlaubt wäre.

Der Compiler würde für PinDrop die gleichen Einschränkungen anwenden wie für Drop und außerdem sicherstellen, dass ein Typ nicht sowohl Drop als auch PinDrop implementiert. Beim Erzeugen von Tropfenkleber wird jede Art von Drop , die der Typ implementiert hat.

Fasst das den Vorschlag zusammen? Der Teil, den ich nicht verstehe, ist Ihr letzter Absatz. Können Sie ein Beispiel geben, das zeigt, worüber Sie sich Sorgen machen?


Jetzt frage ich mich natürlich, was die Beweispflichten hier sind. Ich denke, der einfachste Weg, dies zu sehen, besteht darin zu sagen, dass PinDrop tatsächlich die wichtigste und einzige Art von Destruktor ist, die formal existiert, und impl Drop for T tatsächlich syntaktischer Zucker für impl PinDrop ruft die unsichere Methode PinMut::get_mut und ruft dann Drop::drop . Dies ist automatisch generiert unsicheren Code, aber, weil Pinning eine „lokale Erweiterung“ (dh es ist rückwärts kompatibel mit bestehenden unsicheren Code), dass unsicherer Code ist immer sicher , wenn der angegebene Typ über Pinning kümmert sich nicht darum.

Etwas formeller ausgedrückt gibt es eine "Standard-Pinning-Invariante", die Typen haben, wenn sich der Autor nicht um das Pinning kümmert. Typen, die diese Standard-Pinning-Invariante verwenden, sind automatisch Unpin . Wenn Sie impl Drop für einen Typ mit einer benutzerdefinierten Invariante schreiben, wird bestätigt, dass der Typ die "Standard-Pinning-Invariante" verwendet. Dies ist etwas faul, da es sich ein bisschen so anfühlt, als gäbe es hier eine Beweispflicht, aber es gibt keine unsafe um davor zu warnen - und zwar nicht perfekt, aber Abwärtskompatibilität ist wichtig, also was können Sie tun. Es ist auch keine Katastrophe, denn man kann argumentieren, dass dies wirklich bedeutet, dass es die Beweispflicht, die für unsicheren Code an anderer Stelle entsteht, dahingehend ändert, dass dieser unsichere Code mit der "Standard-Pinning-Invariante" funktionieren muss. Wenn an anderer Stelle in diesem Modul kein unsicherer Code vorhanden ist, ist alles in Ordnung.

Ich könnte mir sogar vorstellen, dass wir einen Flusen gegen impl Drop for T hinzufügen könnten, es sei denn, T: Unpin , möglicherweise beschränkt auf den Fall, dass das Modul, das den Typ definiert, unsicheren Code enthält. Dies wäre ein Ort, um die Leute über das Problem aufzuklären und sie zu ermutigen, entweder unsafe impl Unpin (formell zu behaupten, dass sie die Standard-Pinning-Invariante verwenden) oder impl PinDrop .

@ RalfJung

Fasst das den Vorschlag zusammen?

Ja, mehr oder weniger (ich denke, Ihr Vorschlag ist tatsächlich wesentlich ehrgeiziger als meiner, da er die automatische Durchführung von Projektionen vorschlägt, wenn Drop nicht implementiert ist, was meiner Meinung nach wahrscheinlich ein Problem der Vorwärtskompatibilität für Bibliotheken darstellt, aber vielleicht gibt es einen Ausweg Das).

Der Teil, den ich nicht verstehe, ist Ihr letzter Absatz. Können Sie ein Beispiel geben, das zeigt, worüber Sie sich Sorgen machen?

Im Großen und Ganzen: Ich mache mir Sorgen um zwei Felder, die direkt auf derselben Struktur leben, von denen eines das andere mutiert, wenn sein Drop aufgerufen wird (möglicherweise unter Berufung auf Dinge wie Drop-Reihenfolge aus Sicherheitsgründen), so dass das Feststecken von Invarianten der anderes Feld, behält aber seine strukturelle Integrität bei (so dass Sie Verstöße gegen die Pinning-Invariante beobachten können). Dies kann offensichtlich keinen vollständig sicheren Code verwenden (oder er würde unter anderem durch Dropck abgelehnt werden), daher geht es mir nur um einen hypothetischen Fall, in dem die Destruktoren auf den Feldtypen sicher ausgeführt werden konnten, wenn die Felder getrennt angeheftet wurden Strukturen, aber nicht sicher, wenn sie in derselben Struktur fixiert sind. Ich hoffe, dass es keine solchen Fälle gibt, es sei denn, es gibt eine gemeinsame Invariante, die die Struktur enthält, in der die Felder enthalten sind. Wenn es eine solche Invariante gibt, wissen wir, dass sie sich bewusst sein muss, dass ihre Komponenten die benutzerdefinierte Invariante nicht richtig respektieren, und wir können daher Code irgendwo im Modul beschuldigen.

Ich denke, der einfachste Weg, dies zu sehen, besteht darin, zu sagen, dass PinDrop tatsächlich die wichtigste und einzige Art von Destruktor ist, die formal existiert, und impl Drop for T ist tatsächlich syntaktischer Zucker für impl PinDrop, der die unsichere Methode PinMut :: get_mut aufruft und dann aufruft Drop :: drop.

Einverstanden. Leider war dieser Weg für die aktuelle Lösung nicht offen, da versucht wird, Dinge in einer Bibliothek zu tun.

Etwas formeller ausgedrückt gibt es eine "Standard-Pinning-Invariante", die Typen haben, wenn sich der Autor nicht um das Pinning kümmert. Typen, die diese Standard-Pinning-Invariante verwenden, werden automatisch entfernt. Das Schreiben von impl Drop für einen Typ mit einer benutzerdefinierten Invariante bestätigt, dass der Typ die "Standard-Pinning-Invariante" verwendet. Dies ist etwas faul, da es sich ein bisschen so anfühlt, als gäbe es hier eine Beweispflicht, aber es gibt keine unsichere Warnung, und dies ist zwar nicht perfekt, aber die Abwärtskompatibilität ist wichtig. Was können Sie also tun?

Ja, das ist ungefähr das, was ich vorhabe ... Ich mache mir nur Sorgen, dass ich keine gute Vorstellung davon habe, was es bedeuten würde, diese Garantie für "unsicheren Code im Modul" tatsächlich zu formalisieren. Ich mag Ihre Vorstellung von einem Fussel (ich mag alles, was mehr Leute dazu bringt, PinDrop zu verwenden!), Aber ich denke, unsafe impl Unpin wäre wahrscheinlich viel zu oft falsch, als dass es eine gute Sache wäre, dies vorzuschlagen Zumindest für Typen mit generischen öffentlichen Feldern (aber andererseits für Strukturen, die solche Felder nicht haben, würde es tatsächlich ziemlich oft zutreffen ... es trifft zum Beispiel weitgehend auf die Standardbibliothek zu).

Ich habe ein Beispiel geschrieben, wie #[derive(PinnedFields)] funktionieren könnte: https://github.com/withoutboats/derive_pinned

Ich habe Leute behaupten sehen, dass solche Ableitungen "nicht gesund" sind, aber afaik, das ist nicht wahr. Sie müssten unsicheren Code verwenden, um etwas zu tun, das mit dem durch diese Ableitung generierten Code in Konflikt steht - das heißt, dies macht anderen unsicheren Code unsicher (und ich denke, dass Code, der sich um ?Unpin Daten bewegt, etwas ist, das Sie können / sollten immer vermeiden).

EDIT: Okay, lesen Sie tatsächlich die letzten paar Beiträge über Destruktoren. Wird verarbeiten.

@withoutboats Ja, ich denke, Sie haben dies bereits gesehen, aber das Problem war, dass unsicherer Code in einem korrekten !Unpin -Typ von einem sicheren Destruktor für einen Typ ungültig gemacht werden konnte, der PinFields (also) abgeleitet hat Es gab keinen unsicheren Code im Modul für den Typ, der PinFields Ausnahme des automatisch generierten Materials. Das ist der problematische Teil. Schauen Sie sich jedoch das von mir verknüpfte Design an (ignorieren Sie die stilistische Entscheidung, eine separate Struktur zu erstellen, anstatt einzelne Accessoren abzuleiten - ich habe nur versucht, es so viele Anwendungsfälle wie möglich mit dem Leihprüfer zu unterstützen). Ich war eine Weile besorgt, aber ich bin mir jetzt ziemlich sicher, dass #[derive(PinFields)] noch funktionieren kann. Es muss nur darauf geachtet werden, dass Drop nicht direkt implementiert wird.

Ich möchte noch einen weiteren Punkt ansprechen, über den ich nachgedacht habe (den ich noch nirgendwo direkt gelöst gesehen habe?): Ich denke etwas, das Pin viel benutzerfreundlicher macht und sich besser in vorhandenen Code integriert. wäre, fest auf die Seite von im Wesentlichen allen Zeigertypen zu kommen, die Unpin . Das heißt, &mut T , &T , *const T , *mut T , Box<T> und so weiter werden alle als Unpin für jedes T . Es mag zwar faul erscheinen, zuzulassen, dass beispielsweise Box<T> Unpin , aber es ist sinnvoll, wenn man bedenkt, dass man keine Pin in das Innere von bekommen kann die Box aus einem Pin<Box<T>> . Ich denke, dass es ein sehr vernünftiger Ansatz ist, nur !Unpin zu erlauben, Dinge zu infizieren, die "inline" sind - ich habe keinen einzigen Anwendungsfall, um zuzulassen, dass Pinning über Referenzen hinweg viral wird, und es macht die Semantik von Jeder mögliche &pin -Typ ist sehr ansprechend (ich habe eine Tabelle ausgearbeitet, wie er mit den anderen Zeigertypen in diesem Szenario interagieren würde, und wenn Sie Bewegungen ignorieren, verhält sich &mut pin im Wesentlichen genauso wie &mut , &pin verhalten sich in Bezug auf die Beziehung zu anderen Zeigern genauso wie & und box pin verhalten sich genauso wie box . Soweit ich das beurteilen kann, ist dies auch betrieblich nicht wichtig: Im Allgemeinen wird durch Verschieben eines Werts vom Typ A , der einen Zeiger auf einen Wert vom Typ B enthält, der Wert von nicht angezeigt Geben Sie B , es sei denn, der Wert von Typ B dem Wert von Typ A aber wenn dies der Fall ist, ist A automatisch !Unpin da es B ohne Zeiger-Indirektion enthält. Am wichtigsten wäre vielleicht, dass ein großer Prozentsatz der Typen, für die derzeit eine manuelle unsichere Implementierung von !Unpin erforderlich wäre, keine benötigt, da die meisten Sammlungen nur T hinter einer Zeiger-Indirektion enthalten . Dies würde es beiden ermöglichen, dass die aktuellen Drop weiterarbeiten, und diesen Typen ermöglichen, PinDrop zu implementieren, ohne ihre Semantik zu ändern (da ein Typ, der Unpin ist, ein Pin behandeln kann &mut würde sowieso als

Ich vermisse vielleicht einen Grund, warum diese Art des transitiven Fixierens eine gute Idee wäre, aber bisher habe ich keinen gefunden. Die einzige betriebliche Sache, über die ich mir Sorgen machen könnte, ist, ob es tatsächlich möglich ist, dieses Verhalten mit automatischen Merkmalen zu implementieren - ich denke, das würde es wahrscheinlich tun, aber vielleicht in einigen Fällen, in denen Leute PhantomData<T> aber tatsächlich ein Eigentum haben Zeiger auf T , es wäre gut für sie, es in PhantomData<Box<T>> zu ändern. Normalerweise denken wir, dass diese semantisch genau gleich sind, aber mit Pinning ist das nicht ganz richtig.

@pythonesque Die Terminologie einer "neuen Sprache" und dergleichen trübt die Situation für mich. Mein Eindruck von dem, was Sie tun, ist:

  • Standardmäßig generiert #[derive(PinFields)] ein No-Op Drop impl. Dies garantiert, dass Sie niemals auf das angeheftete Feld im Destruktor zugreifen.
  • Ein optionales Attribut ändert dieses Drop impl, um PinDrop::pin_drop aufzurufen, und Sie sollten PinDrop implementieren.

Ist das richtig?


Ich glaube auch, dass diese ganze Sache nur wichtig war, wenn wir die Garantien von Pin , um aufdringliche Sammlungen zu unterstützen. Stimmt dies mit Ihrem Verständnis von @RalfJung und @pythonesque überein?


All dies ist ziemlich frustrierend, denn das, was klar zu sein scheint , ist , dass Drop nur selbst durch nehmen soll Pin ! Eine radikalere (möglicherweise epochale) Veränderung vorzunehmen scheint ansprechend, aber ich sehe keinen Weg, der nicht sehr störend ist.

Hier ist ein Beispiel für die Art von Unklarheiten, die der Zugriff auf angeheftete Felder + Löschen verursachen kann: https://play.rust-lang.org/?gist=8e17d664a5285e941fe1565ce0eca1ea&version=nightly&mode=debug

Der Typ Foo übergibt einen internen Puffer an eine externe API, bei der der Puffer so lange verbleiben muss, bis er explizit nicht mehr verbunden ist. Soweit mir bekannt ist, ist dies unter den von @cramertj vorgeschlagenen Einschränkungen Pin<Foo> erstellt wurde, wird Ihnen garantiert, dass es erst verschoben wird, nachdem Drop darauf aufgerufen wurde (mit die Maßgabe, dass es stattdessen durchgesickert sein könnte und Drop niemals aufgerufen wird, aber in diesem Fall ist garantiert, dass es niemals verschoben wird).

Der Typ Bar unterbricht dies dann, indem er die Foo während seiner Implementierung von Drop .

Ich verwende eine sehr ähnliche Struktur wie Foo , um ein Funkperipheriegerät zu unterstützen, das über DMA kommuniziert. Ich kann ein StableStream mit einem internen Puffer haben, in den das Funkgerät schreibt.

@ohne Boote

Ist das richtig?

Ja, außer dass es kein No-Op Drop impl generiert (weil Typen, die Drop in Rust nicht implementieren, im Allgemeinen besser funktionieren). Stattdessen wird versucht zu behaupten, dass Drop nicht mit einigen Funktionen der Fischbibliothek implementiert wurde (es funktioniert stabil, bricht aber unter Spezialisierung ab - ich denke, es gibt eine Variante, die unter Spezialisierung funktionieren

Ich glaube auch, dass diese ganze Sache nur wichtig war, wenn wir die Garantien von Pin erweitert haben, um aufdringliche Sammlungen zu unterstützen. Stimmt dies mit Ihrem Verständnis von @ralfj und @pythonesque überein?

Nein, das ist leider nicht der Fall. Das oben verlinkte Gegenbeispiel hat nichts mit den zusätzlichen Garantien für aufdringliche Sammlungen zu tun. Ein Pin 'd-Typ muss bereits in der Lage sein anzunehmen, dass er sich auch ohne diese Garantie nicht bewegt, bevor er erneut verwendet wird, da der Wert eine Methode hat, die zweimal für einen Wert hinter einer angehefteten Referenz aufgerufen wird keine Möglichkeit zu wissen, ob es zwischen den beiden Anrufen verschoben wurde. Die zusätzlichen Garantien, die erforderlich sind, um es für aufdringliche Sammlungen nützlich zu machen, fügen eine zusätzliche Sache hinzu, dass Sie drop aufrufen müssen, bevor der Speicher freigegeben wird, aber auch ohne diese Garantie können drop immer noch für etwas aufgerufen werden Das ist derzeit fixiert (zum Beispiel hinter einer PinBox). Wenn das abgelegte Objekt Inline-Felder enthält und wir die Projektion des Pins von dem abgelegten Objekt auf diese Felder zulassen, kann der Destruktor des äußeren Typs das innere Feld weiterhin verschieben und erneut anheften (indem er das ausgeblendete Feld setzt) Feldwert in einem PinBox (zum Beispiel) und rufen Sie dann Methoden darauf auf, die erwarten, dass Referenzen von dem Zeitpunkt, an dem sie zuvor angeheftet wurden, noch gültig sind. Ich sehe keinen Weg daran vorbei; Solange Sie drop implementieren können, kann dieses Problem für jedes Inline-Feld !Unpin . Aus diesem Grund denke ich, dass die am wenigsten schlechte Lösung darin besteht, die Leute nicht drop implementieren zu lassen, wenn sie feststecken möchten, um richtig zu funktionieren.

All dies ist ziemlich frustrierend, denn es scheint klar zu sein, dass Drop sich einfach selbst durch Pin nehmen sollte! Eine radikalere (möglicherweise epochale) Veränderung vorzunehmen scheint ansprechend, aber ich sehe keinen Weg, der nicht sehr störend ist.

Ja, es ist wirklich nervig ... Ich war ungefähr eine Woche lang sehr mürrisch. Aber wie Ralf betonte, hätten wir noch drei Jahre auf 1.0 warten müssen, bevor jemand dies herausgefunden hätte ... und es wird immer etwas mehr geben.

Wenn das Objekt, das gelöscht wird, Inline-Felder enthält und wir zulassen, dass der Pin von dem Objekt, das gelöscht wird, auf diese Felder projiziert wird, kann der Destruktor des äußeren Typs das innere Feld trotzdem verschieben und dann Methoden aufrufen, von denen Referenzen erwartet werden als es angeheftet wurde, um noch gültig zu sein.

Der hervorgehobene Teil scheint mir eine sehr große Sache zu sein; in der Tat scheint es der Kern des Problems.

Anfangs stellte ich mir diesen Code vor, der unsere einzige Methode verwendet, die sich tatsächlich um interne Adressen kümmert:

struct TwoFutures<F>(F, F);

impl Drop for TwoFutures {
     fn drop(&mut self) {
          mem::swap(&mut self.0, &mut self.1);
          unsafe { Pin::new_unchecked(&mut self.0).poll() }
     }
}

Aber es beinhaltet unsicheren Code, um zum Pin ! Dieser Code sollte nur als nicht korrekt angesehen werden.

Können wir die Möglichkeit ausschließen, dass Methoden, die &self und &mut self benötigen, sich auf die Gültigkeit interner Adressen für die Solidität verlassen? Wie sieht das aus?

@withoutboats Auch wenn nur Methoden, die Pin<Self> benötigen, auf die Gültigkeit interner Adressen für die Solidität angewiesen sind, kann das Problem dennoch auftreten. Der Destruktor des äußeren Typs kann mem::replace das Feld des inneren Typs (unter Verwendung seiner eigenen &mut self Referenz), dann PinBox::new den Wert und dann eine angeheftete Methode darauf aufrufen . Keine unsichere erforderlich. Der einzige Grund, warum es kein Problem ist, ohne angeheftete Felder projizieren zu können, ist, dass es für einen Typ als akzeptabel angesehen wird, der seltsame !Unpin -Methoden mit unsafe implementiert (oder eine Invariante mit einem Typ teilt, der dies tut ) Beweispflichten für die Implementierung von drop , auch wenn dort nur sicherer Code verwendet wird. Aber Sie können das nicht von Typen verlangen, die zufällig einen Typ enthalten , der !Unpin und keinen eigenen unsicheren Code hat.

@pythonesque

Ich denke, Ihr Vorschlag ist tatsächlich wesentlich ehrgeiziger als meiner, da er die automatische Durchführung von Projektionen vorschlägt, wenn Drop nicht implementiert ist, was meiner Meinung nach wahrscheinlich ein Problem der Vorwärtskompatibilität für Bibliotheken darstellt. aber vielleicht gibt es einen Ausweg

Nun, ich denke, wir wollen das irgendwann tun. Wie ist das ein Kompatibilitätsproblem?

Ich mache mir Sorgen um zwei Felder, die direkt auf derselben Struktur leben, von denen eines das andere mutiert, wenn sein Drop aufgerufen wird (möglicherweise unter Berufung auf Dinge wie die Drop-Reihenfolge aus Sicherheitsgründen), so dass das Fixieren von Invarianten des anderen Feldes verletzt wird. Bewahrt jedoch seine strukturelle Integrität (so können Sie Verstöße gegen die Pinning-Invariante beobachten).

Das wäre aber momentan schon illegal. Sie dürfen die Invarianten anderer nicht verletzen.

Aber ich denke, unsicheres Unpin wäre wahrscheinlich viel zu oft falsch, als dass es eine gute Sache wäre, zumindest für Typen mit generischen öffentlichen Feldern

Ich denke, dass es die meiste Zeit tatsächlich korrekt sein wird - ich gehe davon aus, dass die meisten Leute keine Zugriffsmethoden für Pin<Self> bereitstellen werden, und in diesem Fall verwenden sie wahrscheinlich die Standard-Pinning-Invariante, und daher ist es für unsafe impl Unpin Ordnung

Ich denke, etwas, das Pin viel benutzerfreundlicher machen und besser in vorhandenen Code integrieren würde, wäre, fest auf die Seite von im Wesentlichen allen Zeigertypen zu fallen, die Unpin sind. Das heißt, & mut T, & T, * const T, * mut T, Boxund so weiter alle als Unpin für jedes T betrachtet werden. Während es faul erscheinen mag, etwas wie beispielsweise Box zuzulassenUm Unpin zu sein, ist es sinnvoll, wenn man bedenkt, dass man aus einem Pin keinen Pin in das Innere der Box bekommen kann>.

Ich bin damit einverstanden, dass dies wahrscheinlich passieren wird (siehe auch meinen Kommentar unter https://github.com/rust-lang/rust/pull/49621#issuecomment-378286959). Momentan denke ich, wir sollten ein bisschen warten, bis wir uns in der gesamten Pinning-Sache sicherer fühlen, aber in der Tat sehe ich sehr wenig Sinn darin, das Pinning über Zeiger-Indirektionen hinaus durchzusetzen.
Ich bin mir nicht sicher, wie ich das mit rohen Zeigern machen soll. Wir sind normalerweise äußerst konservativ in Bezug auf Autoeigenschaften für sie, da sie auf alle möglichen verrückten Arten verwendet werden.


@ohne Boote

Ich habe ein Beispiel geschrieben, wie # [ableiten (PinnedFields)] funktionieren könnte: https://github.com/withoutboats/derive_pinned

@pythonesque hat dies bereits gesagt, dies aber nur um klar zu sein: Diese Bibliothek ist nicht in Ordnung. Wenn ich es mit @MicahChalmers Drop-Problem verwende, kann ich jeden angehefteten Typ brechen, der tatsächlich auf dem Anheften beruht. Dies ist unabhängig von der zusätzlichen Garantie, dass Drop abgerufen wird. Lassen Sie mich wissen, ob noch ein Beispiel benötigt wird. ;)

@ RalfJung

Diese Bibliothek ist nicht in Ordnung.

Zur Verdeutlichung ist die Ableitung nur unsafe und kann nicht in Kombination mit einem manuellen Drop impl verwendet werden. Durch die Verwendung des Derivats versprechen Sie, keine der in einer Implementierung von Drop aufgeführten schlechten Dinge zu tun.

https://github.com/rust-lang/rust/pull/50497 ändert die Pinning-API. Vor allem wird Pin in PinMut , um Platz für das Hinzufügen eines freigegebenen Pin in der Zukunft.


Zur Verdeutlichung ist die Ableitung nur unsicher und kann nicht in Kombination mit einem manuellen Drop-Gerät verwendet werden.

Einverstanden, es so unsicher zu machen würde funktionieren. Obwohl der Test den Eindruck

@RalfJung Richtig, ich glaube nicht, dass es im Moment ist. Eine andere Option, an die ich gerade gedacht habe, wäre, dass die "sichere" Version eine Implementierung von Drop für den Typ erstellt und andere manuelle Implikationen von Drop blockiert. Es könnte eine unsafe -Flagge geben, um dies auszuschalten. WDYT?

@cramertj ja das sollte auch funktionieren. Es hätte jedoch Nebenwirkungen wie ein restriktiveres Dropck und die Unfähigkeit, Felder zu verlassen.

@pythonesque und ich hatten am Montag einen Anruf, um dies zu besprechen. Hier ist eine Zusammenfassung.

Wir kamen zu dem Schluss, dass das "richtige" Verhalten wahrscheinlich darin bestanden hätte, dass Drop sich selbst per Pin genommen hätte. Der Übergang dazu ist jedoch entmutigend. Obwohl es mit einer Änderung der Ausgabe in irgendeiner Form möglich ist, glaube ich, wäre dies äußerst störend.

Eine abwärtskompatible Änderung besteht darin, sie für Typen, die "pin-projiziert" werden können, um Drop zu implementieren, nur irgendwie inkohärent zu machen. In seinem Repository hat @pythonesque dies implementiert, indem es Impls aus einem verschlungenen Satz von Merkmalen generiert.

Man könnte sich eine eingebaute Form vorstellen, die etwas einfacher ist:

  • Ein Markierungsmerkmal, nennen wir es PinProjection , steuert, ob ein Typ per Pin projiziert werden kann oder nicht. Es ist inkohärent (durch integrierte Compiler-Magie), sowohl PinProjection als auch Drop zu implementieren.
  • Ein weiteres Merkmal, PinDrop , erweitert PinProjection , um einen alternativen Destruktor zu Drop bereitzustellen.

Ableitungen zum Generieren von Pin-Projektionsmethoden wie die von @pythonesque und ich haben gezeigt, dass auch Impls von PinProjection für den Typ generiert werden.

@ohne Boote

Wir kamen zu dem Schluss, dass das wahrscheinlich "richtige" Verhalten darin bestanden hätte, dass Drop sich selbst per Stecknadel genommen hätte. Der Übergang dazu ist jedoch entmutigend. Obwohl es mit einer Änderung der Ausgabe in irgendeiner Form möglich ist, glaube ich, wäre dies äußerst störend.

Ich frage mich nur, ob wir eines Tages so etwas tun wollen. Ist das, was Sie vorschlagen, zukunftsfähig?

Nehmen wir zum Beispiel an, wir haben beschlossen, ...

  • make Drop akzeptiert
  • Schreibe alle Drop Signaturen für sie im Rahmen des automatischen Upgrades auf Rust 2021 neu :)

(Ich schlage keines davon vor, aber es scheint schwierig, die Frage ohne konkrete Beispiele zu beantworten.)

@tmandry Das ist im Wesentlichen die Idee.

Das große Problem ist eine generische Drop -Implementierung, die den Typparameter verschiebt:

struct MyType<T>(Option<T>);

impl<T> Drop for MyType<T> {
    fn drop(&mut self) {
        let moved = self.0.take();
    }
}

Die Art von automatischem Upgrade, von der Sie sprechen, würde dieses Drop-Impl einfach beschädigen. Dies ist nur gültig, wenn wir eine Grenze hinzufügen, die T: Unpin .

Ich kenne keine gültigen Anwendungsfälle für das tatsächliche Verschieben von ?Unpin -Daten in einem Destruktor. Es ist also nur dieser Fall, in dem dies technisch möglich ist, aber nicht beabsichtigt ist. Das ist wirklich ein Problem.

@ohne Boote

Ein weiteres Merkmal, PinDrop, erweitert PinProjection, um einen alternativen Destruktor zu Drop bereitzustellen.

Warum verlängert PinDrop PinProjection ? Das scheint unnötig.

Dies könnte auch etwas schlauer gemacht werden, indem man sagt, dass PinProjection und Drop nicht kompatibel sind, es sei denn, der Typ ist Unpin (oder man findet einen anderen Weg, um all diese neuen Unterscheidungen / Einschränkungen zu entfernen für Unpin Typen).

@RalfJung wir möchten, dass sich PinDrop und Drop irgendwie gegenseitig ausschließen. Letztendlich gibt es einige Möglichkeiten, dies mit unterschiedlichen Kompromissen umzusetzen. Ich denke, wir brauchen einen RFC dafür, obwohl es keine kleine Änderung ist.

@ohne Boote vereinbart. Ich dachte, die Exklusivität könnte sich aus einer Sonderbehandlung von PinDrop , aber es gibt offensichtlich mehrere Möglichkeiten, dies zu tun. Ich denke, es wäre sehr nützlich, wenn sich Unpin -Typen nicht darum kümmern müssten; Zusammen mit der bedingungslosen Einstellung aller Zeigertypen Unpin dies wahrscheinlich dazu beitragen, die Anzahl der Fälle zu verringern, in denen die Leute überhaupt darüber Bescheid wissen müssen.

@withoutboats Erwähnenswert ist auch, dass die wichtigsten Instanzen, an die ich denken kann, wo Leute beispielsweise generische Daten in einem Destruktor in einem Vec verschieben möchten (ich habe Vec weil IIRC-Leute dies möchten Implementieren Sie Methoden auf Vec , die sich tatsächlich um Pin kümmern, was bedeutet, dass Unpin nicht unbedingt implementiert werden kann. Es handelt sich tatsächlich um &mut Vec<T> oder Vec<&mut T> oder so. Ich spreche das meistens an, weil dies Fälle sind, die wahrscheinlich durch die von ausgelöst würden , und hoffentlich können die Leute in diesen Fällen leicht zu PinDrop wechseln, anstatt zu unsafe impl Unpin (Theoretisch können sie das erstere natürlich immer tun, indem sie T mit Unpin im Typ begrenzen, aber das wird eine bahnbrechende Änderung für die Clients der Bibliothek sein).

Es ist auch erwähnenswert, dass dies zwar ein paar mehr Merkmale hinzufügt, als wir möchten, aber diese Merkmale werden so gut wie nie in der Typensignatur eines anderen auftauchen. Insbesondere PinProjection ist eine völlig sinnlose Bindung, es sei denn, Sie schreiben ein #[derive(PinFields)] -Makro, da der Compiler (glaube ich) immer bestimmen kann, ob PinProjection gilt, wenn dies möglich ist Finden Sie heraus, welche Felder der Typ enthält, und das einzige, wofür es nützlich ist, ist das Projizieren von Feldern. Ebenso sollte PinDrop im Grunde genommen niemals eine Bindung für irgendetwas sein müssen, aus dem gleichen Grund, aus dem Drop fast nie als Bindung verwendet wird. Sogar Merkmalsobjekte sollten weitgehend unberührt bleiben (es sei denn, wir erhalten eines Tages "zugeordnete Felder", aber mit einer solchen neuen Funktion könnten wir vorschreiben, dass Merkmale mit zugeordneten Feldern nur für PinProjection -Typen implementiert werden können, was sich ordentlich lösen lässt dieses Problem).

@ RalfJung

Nun, ich denke, wir wollen das irgendwann tun. Wie ist das ein Kompatibilitätsproblem?

Ich nehme an, es ist nicht mehr als ein Typ, der Send oder Sync implementiert, mit dem Unterschied, dass keiner von beiden die Kernsprache in dem Maße beeinflusst, wie dies der Fall wäre. Es scheint völlig automatisch zu sein, wie Rust Copy (Typen waren immer Copy wenn sie nur Copy Typen enthielten und Drop nicht implementierten ), die schließlich geändert wurde, um es explizit zu machen, weil es den Leuten vermutlich nicht gefiel, dass das Hinzufügen eines Merkmals ( Drop ) generischen Code beschädigen konnte, ohne dass sie es bemerkten (da der ganze Punkt der Kohärenz so zusätzlich ist Die Implementierung von Merkmalen sollte keine nachgeschalteten Kisten beschädigen. Dies scheint fast identisch zu sein, nur mit PinProjection anstelle von Copy . Ich mochte das alte Verhalten wirklich, ich denke nur, es wäre schwierig zu rechtfertigen, dass PinProjection funktioniert, wenn Copy dies nicht tut.

Das wäre aber momentan schon illegal. Sie dürfen die Invarianten anderer nicht verletzen.

Ja, je mehr ich darüber nachdenke, desto weniger plausibel scheint es, dass es ein Problem sein könnte.

Ich denke, dass es die meiste Zeit tatsächlich richtig sein wird

Ja, aber nur, weil die meisten Typen keine Methoden implementieren, die Pin erfordern oder ihre generischen Felder als öffentlich verfügbar machen. Während sich letzteres wahrscheinlich nicht ändern wird, ist es das erstere nicht - ich erwarte, dass zumindest einige der stdlib-Typen, die derzeit !Unpin , Pin-Projektionsmethoden hinzufügen, zu welchem ​​Zeitpunkt die unsafe -Implementierung dies nicht tun würde ist nicht mehr gültig. Es scheint mir also eine ziemlich fragile Sache zu sein. Darüber hinaus mache ich mir Sorgen, dass die Menge an unsicherem Code, den die Leute schreiben müssen, erhöht wird. Ich denke, Send und Sync Grenzen sind unsafe impl ungefähr so ​​oft korrekt, wie sie falsch implementiert wurden, und Unpin wäre noch heimtückischer, weil die Normalerweise hat die richtige Version keine Grenzen für T . Es scheint also weitaus vorzuziehen, die Leute in Richtung PinDrop zu lenken (ich verstehe jedoch, warum Sie vorsichtig sind, dies für rohe Zeigertypen zu tun. Ich mache mir nur Sorgen, dass bereits- unsafe Code wird Es ist sogar noch wahrscheinlicher, dass Sie ein unsafe impl ohne darüber nachzudenken, aber je mehr ich darüber nachdenke, desto mehr scheint es, als ob die Standardeinstellung für Rohzeiger wahrscheinlich korrekt ist und es nützlich wäre, sie mit Ihren Flusen zu kennzeichnen.

Ich denke, es wäre sehr nützlich, wenn sich Unpin-Typen nicht darum kümmern müssten.

Ich stimme dem zu, aber da die Leute, wie ich bereits sagte, PinProjection als tatsächliche Bindung verwenden werden, bin ich mir nicht sicher, wie wichtig dies in der Praxis sein würde. Es gibt bereits eine Implementierung von DerefMut für PinMut bei der T: Unpin sodass Sie heute keinen Nutzen daraus ziehen würden. Eine im Compiler implementierte Regel (für Projektionen) würde vermutlich PinMut::new in eine Art &pin Reborrow für Unpin Typen verwandeln, aber das hat eigentlich nichts mit Feldprojektionen zu tun. Und da das Ableiten von PinProjection keine PinProjection für seine Felder erfordert, würden Sie es nicht nur benötigen, um die Grenzen eines derive anderen Typs zu erfüllen. Also wirklich, was ist der Gewinn? Das Einzige, was Sie wirklich tun können, ist, Drop implementieren und gleichzeitig PinProjections abzuleiten, aber wir möchten immer, dass die Leute PinDrop implementieren, wenn dies möglich ist eine negative Netto-IMO sein.

Ich habe mich für Vec entschieden, weil IIRC-Leute gerne Methoden auf Vec implementieren möchten, die sich tatsächlich um Pin kümmern, was bedeutet, dass Unpin nicht unbedingt implementiert werden kann

Hm, ich glaube nicht, dass mir das gefällt. Wenn Box bedingungslos Unpin , sollte Vec gleich sein. Die beiden Typen sind in ihrem Besitz normalerweise ziemlich gleichwertig.

Wir müssen auch vorsichtig mit der drop Garantie sein; Vec::drain zum Beispiel dazu führen, dass im Falle einer Panik etwas durchgesickert ist.

Dies scheint fast identisch zu sein, nur mit PinProjection anstelle von Copy

Oh, jetzt verstehe ich deine Frage. Ich habe eigentlich nicht über das automatische Ableiten von PinProjections da mein Vorschlag keine solche Eigenschaft hatte - aber eine Konsequenz meines Vorschlags wäre, dass das Hinzufügen von Drop eine bahnbrechende Änderung ist, wenn Sie öffentliche Felder haben.

Das einzige, was Sie wirklich tun können, ist, Drop zu implementieren und PinProjections gleichzeitig abzuleiten, aber wir möchten immer, dass die Leute PinDrop implementieren, wenn dies möglich ist, so dass dies eine negative IMO wäre.

Das war eigentlich der Punkt. Je weniger Leute sich um all diese Dinge kümmern müssen, desto besser.

Klärungsfrage: In Ihrem und @withoutboats 'Vorschlag ist PinDrop ein langes Element und wird vom Compiler als Ersatz für Drop , oder ist es nur das Merkmal, das von derive(PinProjections) um Drop zu implementieren?

Auch müssen wir auf die Drop-Garantie achten; Vec :: Drain kann zum Beispiel dazu führen, dass im Falle einer Panik etwas ausläuft.

Nun, das stimmt, aber es wird tatsächlich kein Speicher freigegeben, richtig? Um es klar, ich würde eigentlich lieber , wenn Vec bedingungslos umgesetzt hat Unpin , da es würde es für die Menschen leichter, nur Schalter auf PinDrop , aber es war auf jeden Fall redet in Einige der Pinning-Diskussionen über das Anheften an Backing-Elemente. Sobald Sie über das Anheften von Feldern sprechen, wird klar, dass Sie die Vec nicht immer einfach in ein Box<[T]> an Ort und Stelle verwandeln und dann anheften können, sodass sie möglicherweise tatsächlich einen Wert von haben dieser Punkt (obwohl Sie natürlich auch einen PinVec -Typ anstelle eines Vec -Typs hinzufügen könnten, wie dies für Box getan wurde).

Das war eigentlich der Punkt. Je weniger Leute sich um all diese Dinge kümmern müssen, desto besser.

Hm, aus meiner Sicht wäre das zunächst richtig, aber auf lange Sicht möchten wir so viele Leute wie möglich auf einen Standardwert von PinDrop migrieren, insbesondere wenn der Typ tatsächlich auf #[derive(PinProjections)] stört Unpin wäre - es würde wahrscheinlich nur in Sachen wie generiertem Code vorkommen). Dann (vielleicht nachdem &pin eine Weile in der Sprache war) könnten Sie eine Epochenänderung haben, die Drop auf DeprecatedDrop oder so umstellte. Für Unpin Typen würde das Ändern der Typensignatur von &mut auf &mut pin so ziemlich alles lösen, sodass dies möglicherweise nicht wirklich benötigt wird.

Klärungsfrage: Ist PinDrop in Ihrem und dem Vorschlag von @withoutboats ein langes Element und wird vom Compiler als Ersatz für Drop behandelt, oder ist es nur das Merkmal, das von deriv (PinProjections) zur Implementierung von Drop verwendet wird?

Das Vorherige.

Diese ganze Drop-Diskussion scheint ein ziemlich drastisches Versehen im Namen des ursprünglichen RFC zu sein. Wäre es wertvoll, einen neuen RFC zu eröffnen, um dies zu diskutieren? Es ist etwas schwierig zu verfolgen, was die Bedenken sind, welche Bedenken schädlicher sind als andere, genau wie viel mehr Maschinen wir benötigen, um die Sprache zu erweitern, als wir ursprünglich dachten, wie viel Bruch wir erwarten sollten (und ob Es kann durch die Ausgabe gemildert werden) im schlimmsten und besten Fall, wie wir zu etwas übergehen könnten, das Drop erfolgreich ist, und ob wir auf all dies zurückgreifen und unsere Ziele für 2018 auf andere Weise erreichen könnten (wie z. B. wie https: //github.com/rust-lang/rfcs/pull/2418 schlägt vor, Pin vor allen öffentlichen APIs auszublenden, um die Stabilisierung nicht zu blockieren.

@bstrie Ich denke, es gibt nur ein großes &pin mut -Typ diese Art von Code irgendwann sicher machen könnte:

struct Foo<T> { foo : T }

fn bar<T>(x: &pin mut Foo<T>) -> &pin mut T {
  &pin mut x.foo
}

Es ist wichtig, dass diese Art von Code sicher ist, da angeheftete Referenzen in der Praxis ansonsten nicht wirklich zusammensetzbar sind. Andernfalls würde die Implementierung eines der Futures-Kombinatoren die Verwendung von unsafe erfordern. Zu der Zeit wurde angenommen, dass die Verwendung von unsafe in den meisten Fällen aufgehoben werden könnte und daher keine große Sache wäre.

Ob dies sicher ist, hängt leider von der Implementierung von Drop für Foo . Wenn wir also sichere Feldprojektionen unterstützen möchten (sei es über eine Sprachfunktion, ein benutzerdefiniertes #[derive] oder einen anderen Mechanismus), müssen wir garantieren können, dass solche schlechten Drop -Implementierungen möglich sind. t passieren.

Die Alternative, die am besten zu funktionieren scheint, ermöglicht, dass der obige Code ohne die Verwendung von unsafe (nur mit dem Zusatz von #[derive(PinProjections)] über der Struktur) funktioniert und keinen vorhandenen Code beschädigt . Es kann abwärtskompatibel hinzugefügt werden, auch wenn Pin bereits stabilisiert ist, und kann sogar als reine Bibliothek hinzugefügt werden (mit einem starken ergonomischen Treffer). Es ist sowohl mit #[derive] Makros zum Generieren von Accessoren als auch mit einem eventuellen nativen Referenztyp von &pin kompatibel. Während mindestens ein neues Merkmal hinzugefügt werden muss (und möglicherweise zwei, je nachdem, wie die angeheftete Version von Drop implementiert ist), müssen sie niemals irgendwo in where -Klauseln oder Merkmalsobjekten erscheinen, und Die neue Version von drop nur für Typen implementiert werden, die derzeit eine Implementierung von Drop und sich für Pin-Projektionen entscheiden möchten.

Nur weil eine Lösung nicht viele Nachteile zu haben scheint, heißt das nicht, dass es sich nicht um eine wesentliche Änderung handelt. Ich denke daher, dass wir mit ziemlicher Sicherheit einen RFC für diesen Vorschlag erstellen werden ( @withoutboats und ich haben dies beim Aufruf besprochen). . Es wäre in Ordnung, isoliert darauf zu stoßen, da es vollständig abwärtskompatibel ist. Persönlich halte ich es jedoch für sinnvoller, diese Änderung durchzusetzen, als sich nicht an anderer Stelle festzunageln.

Die meisten Leute sind besorgt über PinMut in öffentlichen APIs, dass genau dies entweder unsafe erfordert oder überall Unpin Grenzen durchläuft, und dieser Vorschlag löst dieses Problem. Die Alternativen diskutiert in Rost-lang / RFCs # 2418 scheinen viel mehr umstritten, sowohl in den eigentlichen Mechanik der Art und Weise sie will mit Pinning vermeiden , die sich (was die Verbreitung verschiedener anderer Merkmale beinhaltet , die in öffentlichen APIs und Dokumentation erscheinen wird) und in der Gesamtkomplexität der Lösung. Selbst wenn das Fixieren vollständig gelöst wäre, denke ich, dass es eine Reihe von Fragen gibt, die mit diesem RFC nicht angemessen gelöst wurden. Daher besteht meiner Meinung nach zumindest eine gute Chance, dass ein RFC, der sichere Pin-Projektionen hinzufügt, am Ende landet davor akzeptiert werden.

Es ist wahr, dass das Feststecken noch in den Anfängen steckt (und ich weiß, dass ich mich darüber beschwert habe, dass es viel zu schnell stabilisiert wurde), aber ich glaube, dass das Fehlen einer sicheren Feldprojektion das letzte große Problem ist, das die Leute davon abhält, es überhaupt zu verwenden . Der Vollständigkeit halber sind hier alle Probleme aufgeführt, die die Leute beim Fixieren angesprochen haben, ihre vorgeschlagenen Lösungen und ob die Lösung abwärtskompatibel mit dem vorhandenen Typ Pin (in sehr grober, voreingenommener Reihenfolge, wie kontrovers Ich sehe das Problem im Moment als):

  • Dieser (können wir Projektionsfelder in sicherem Code erstellen lassen?). Lösung durch den bevorstehenden RFC (wo möglich werden Implementierungsstrategien dargelegt, sowie andere Alternativen, die wir in Betracht gezogen haben und warum sie verworfen werden mussten). Alle Variationen der vorgeschlagenen Auflösung sind abwärtskompatibel.
  • Sollte das Fixieren eines Werts vom Typ !Unpin mit einem manuellen Destruktor eine zusätzliche Garantie bedeuten (dass der Sicherungsspeicher des Werts erst nach dem Aufruf des Destruktors ungültig wird), auch bekannt als "Änderungen für aufdringliche Sammlungen"?

    Immer noch ungelöst, hauptsächlich, weil dadurch die vorgeschlagene Stack-Pinning-API beschädigt werden könnte. Wenn eine Stack-Pinning-API, die diese Garantie beibehält, dazu gebracht werden kann, mit asynchron zu arbeiten / zu warten, scheinen die meisten Stakeholder bereit zu sein, dies zu akzeptieren (IIRC hat bereits jemand versucht, dies mit Generatoren zu lösen, aber mit ICE-Rustc).

    Nicht abwärtskompatibel mit den alten Garantien; Das Erfordernis eines unsicheren Codes zur Durchsetzung der Garantie ist vorerst mit beiden möglichen Ergebnissen kompatibel, jedoch nur, wenn der unsichere Code nicht darauf angewiesen ist, dass er aus Gründen der Richtigkeit durchgesetzt wird. Dies erfordert also sicherlich eine Auflösung auf die eine oder andere Weise, bevor das Fixieren stabilisiert wird.

    Glücklicherweise können Benutzer von Pinning für Future s dies neben der Form der Stack-Pinning-API ignorieren. Die Closure-basierte Stapel-Pinning-API ist mit beiden Auflösungen kompatibel, sodass Future Benutzer, die nicht async / await verwenden, die Closure-basierte API heute verwenden können, ohne darauf zu warten, dass dies entschieden wird. Die Entscheidung wirkt sich am meisten auf Personen aus, die Pinning für andere Zwecke verwenden möchten (z. B. aufdringliche Sammlungen).

  • Sollten rohe Zeiger bedingungslos Unpin ? Immer noch ungelöst (ich denke, ich bin der einzige, der es vorgeschlagen hat, und ich bin immer noch ziemlich 50-50 dabei). Wäre nicht abwärtskompatibel; Ich bezeichne dies vor allem aus diesem Grund als kontrovers.
  • Sollten Standardbibliothekstypen wie Vec bedingungslos Unpin , oder sollten ihnen Pin Feldzugriffe hinzugefügt werden? Immer noch ungelöst und muss möglicherweise von Fall zu Fall gelöst werden. Für jeden gegebenen sicheren Wrapper-Typ ist es abwärtskompatibel, entweder die Accessoren hinzuzufügen oder den Typ bedingungslos Unpin zu machen.
  • Sollte PinMut::deref entfernt werden? Die Antwort scheint im Grunde genommen "Nein" zu sein, da die Vorteile der Beibehaltung die Nachteile bei weitem überwiegen, und es scheint Problemumgehungen für die Fälle zu geben, in denen die Leute es ursprünglich wollten (insbesondere Pin<RefCell<T>> ). Das Ändern wäre rückwärts inkompatibel.
  • Wie sollten wir kurzfristig Feldzugänger bereitstellen (ohne Berücksichtigung der Drop-Probleme)? Noch ungelöst: Zwei bisher vorgestellte Optionen sind https://github.com/withoutboats/derive_pinned und https://github.com/pythonesque/pintrusive. Die Auflösung ist vollständig abwärtskompatibel, da dies nach den Drop-Änderungen in einem Makro nur im Bibliothekscode problemlos erfolgen kann.
  • Wie sollten wir langfristig Feldzugriffe bereitstellen (dh sollte es benutzerdefinierte Rusttypen &mut pin und &pin ? Wie sollte das erneute Ausleihen funktionieren?). Immer noch ungelöst, aber abwärtskompatibel mit allen anderen vorgeschlagenen Änderungen (nach meinem besten Wissen) und kann offensichtlich auf unbestimmte Zeit eingestellt werden.
  • Sollten sichere referenzähnliche Typen unbedingt Unpin ? Scheint gelöst zu sein (ja, das sollten sie). Die Auflösung ist vollständig abwärtskompatibel.
  • Sollten wir zusätzlich zu einem eindeutigen Pin einen gemeinsamen Typ Pin , um Feldzugriffsmöglichkeiten zu ermöglichen (dies funktioniert nicht mit &Pin da dies ein Verweis auf ist eine Referenz)? Die Lösung bestand darin, Pin in PinMut ändern und einen Pin -Typ für den gemeinsam genutzten Fall hinzuzufügen. Dies war nicht abwärtskompatibel, aber die Änderung ( Pin zu PinMut ) wurde bereits vorgenommen, und ich bin mir ziemlich sicher, dass dies bereits effektiv akzeptiert wurde.

Ich denke, das deckt es ziemlich gut ab. Wie Sie sehen können, haben alle (abgesehen von den Rohzeigern und der Deref-Entscheidung, von denen letztere an dieser Stelle weitgehend gelöst zu sein scheint) einen abwärtskompatiblen Weg vorwärts, selbst wenn die Fixierung heute stabilisiert wurde. Noch wichtiger ist, dass die Tatsache, dass wir Feldprojektionen auflösen können, bedeutet, dass die Verwendung eines angehefteten Typs in Ihrer API keine Entscheidung ist, die Sie später bereuen werden.

Zusätzlich zu den oben genannten Fragen gab es weitere Vorschläge, die darauf abzielen, das Feststecken viel grundlegender zu überdenken. Die beiden, von denen ich glaube, dass ich sie am besten verstehe, sind der @comex- Vorschlag, !Unpin -Typen !DynSized , und der Vorschlag steven099 (intern; sorry, ich kenne den Github-Namen nicht) dazu haben einen neuen nicht dimensionierten Pinned Wrapper-Typ, der die Interna unbeweglich macht (ähnlich wie ein ZST-Wrapper).

Die Option !DynSized ist eine ziemlich konservative Funktion (in dem Sinne, dass Rust bereits über ein ähnliches Merkmal verfügt), die den Vorteil hat, dass sie möglicherweise bereits für den Umgang mit undurchsichtigen Typen benötigt wird. In diesem Sinne ist es möglicherweise noch weniger invasiv als die vorgeschlagenen Änderungen an Drop . Es hat auch einen hohen Vorteil: Es löst das Problem automatisch mit Drop da !Unpin Typen !DynSized wären und man daher nicht in der Lage wäre, aus ihnen herauszukommen. Dies würde dazu führen, dass &mut T und &T automatisch als PinMut und Pin fungieren, wo immer T !DynSized , also würden Sie nicht Es ist nicht erforderlich, dass angeheftete Versionen von Typen und Methoden, die mit &mut T häufig normal funktionieren (wenn sie so geändert werden, dass keine DynSized -Bindung erforderlich ist, wenn sie keine benötigen ).

Der Hauptnachteil (neben den üblichen Bedenken um ?Trait ) scheint zu sein, dass ein !Unpin -Typ niemals verschoben werden kann, was sich von der Situation mit derzeit angehefteten Typen deutlich unterscheidet. Dies bedeutet, dass das Erstellen von zwei angehefteten Typen ohne Verwendung einer Referenz nicht wirklich möglich wäre (soweit ich das beurteilen kann), und ich bin mir nicht sicher, wie oder ob es eine vorgeschlagene Lösung dafür gibt. IIRC dies sollte zusammen mit einem &move Vorschlag funktionieren, aber ich bin nicht sicher, wie die beabsichtigte Semantik davon ist. Ich verstehe auch nicht (aus dem Vorschlag), wie Sie sichere Feldprojektionen damit haben könnten, da es auf undurchsichtigen Typen beruht; Es scheint, als müssten Sie im Allgemeinen viel unsicheren Code verwenden, um ihn zu verwenden.

Der Typ Pinned<T> Größe ist im Geiste etwas ähnlich, möchte jedoch das oben genannte Problem umgehen, indem Sie einen Typ in eine ZST einbinden, die ihn unbeweglich macht (effektiv ohne Größe). Rust hat im Moment nichts Vergleichbares: PhantomData enthält eigentlich keine Instanz des Typs, und die anderen Typen mit dynamischer Größe generieren fette Zeiger und erlauben dennoch Bewegungen (unter Verwendung von APIs, die auf size_of_val basieren ?DynSized beheben sollte, also huckepack dieser Vorschlag wahrscheinlich wieder auf dieses Merkmal). Es scheint mir nicht so, als ob dieser Vorschlag das Problem mit Drop tatsächlich behebt, wenn Sie sichere Projektionen zulassen, und er scheint auch nicht mit Deref kompatibel zu sein, also für mich die Vorteile gegenüber Pin sind nicht so klar.

Wenn eine Stack-Pinning-API, die diese Garantie beibehält, dazu gebracht werden kann, mit asynchron zu arbeiten / zu warten, scheinen die meisten Stakeholder bereit zu sein, dies zu akzeptieren (IIRC jemand hat bereits versucht, dies mit Generatoren zu lösen, aber mit ICE-Rustc)

Als Referenz bezieht sich dies wahrscheinlich auf https://github.com/rust-lang/rust/issues/49537, das aus diesem Versuch einer auf verschachtelten Generatoren basierenden Stapel-Pinning-API stammt. Ich bin mir nicht sicher, ob die Lebensdauer dort funktionieren würde, selbst wenn der ICE behoben ist.

Immer noch ungelöst, hauptsächlich, weil dadurch die vorgeschlagene Stack-Pinning-API beschädigt werden könnte. Wenn eine Stack-Pinning-API, die diese Garantie beibehält, dazu gebracht werden kann, mit asynchron zu arbeiten / zu warten, scheinen die meisten Stakeholder bereit zu sein, dies zu akzeptieren (IIRC hat bereits jemand versucht, dies mit Generatoren zu lösen, aber mit ICE-Rustc).

Ich sehe die Closure-basierte Stack-Pinning-API als Lösung dafür, mit einem zukünftigen Weg (sobald &pin ein Sprachprimitiv ist) für etwas Ergonomischeres und vom Compiler überprüftes. Es gibt auch diese makrobasierte

Sollten rohe Zeiger bedingungslos unpin sein? [...] Wäre nicht abwärtskompatibel; Ich bezeichne dies vor allem aus diesem Grund als kontrovers.

Warum ist das Hinzufügen eines impl Unpin for Vec<T> Ihrer Meinung nach abwärtskompatibel, das Gleiche jedoch nicht für Rohzeiger?

Der Hauptnachteil (neben den üblichen Bedenken in Bezug auf? Trait) scheint zu sein, dass ein! Unpin-Typ niemals bewegt werden konnte, was sich von der Situation mit derzeit angehefteten Typen deutlich unterscheidet. Dies bedeutet, dass das Erstellen von zwei angehefteten Typen ohne Verwendung einer Referenz nicht wirklich möglich wäre (soweit ich das beurteilen kann), und ich bin mir nicht sicher, wie oder ob es eine vorgeschlagene Lösung dafür gibt. IIRC sollte dies zusammen mit einem & move-Vorschlag funktionieren, aber ich bin mir nicht sicher, wie die beabsichtigte Semantik davon aussehen soll.

Nun, unter dem von mir bevorzugten Variantenvorschlag von @ steven099 (den ich bevorzugt habe) würden die meisten Benutzer ( Pinned<T> , das T nach Wert enthält, aber !Sized ( und vielleicht !DynSized ; genaues Design für die Merkmalshierarchie ist offen für Bikeshedding). Dies sieht tatsächlich sehr ähnlich aus wie der bestehende Vorschlag, außer dass &'a mut Pinned<T> für Pin<'a, T> . Aber es ist komponierbarer mit aktuellem und zukünftigem Code, der generisch für &mut T (für T: ?Sized ) ist, und es ist abwärtskompatibler mit einem zukünftigen Design für echte, native unbewegliche Typen, die es nicht müssten benutze Pinned .

Um genauer zu sein, könnte Pinned aussehen:

extern { type MakeMeUnsized; }

#[fundamental]
#[repr(C)]
struct Pinned<T> {
    val: T,
    _make_me_unsized: MakeMeUnsized,
}

Im Allgemeinen würden Sie ein Pinned<T> direkt erstellen. Stattdessen können Sie von Foo<T> auf Foo<Pinned<T>> , wobei Foo ein intelligenter Zeiger ist, der garantiert, dass der Inhalt nicht verschoben wird:

// This would actually be a method on Box:
fn pin_box<T>(b: Box<T>) -> Box<Pinned<T>> {
    unsafe { transmute(b) }
}

(Es könnte auch eine Stack-Pinning-API geben, die wahrscheinlich auf einem Makro basiert, deren Implementierung jedoch etwas komplizierter ist.)

In diesem Beispiel steht FakeGenerator für einen vom Compiler generierten Generatortyp:

enum FakeGenerator {
    Initial,
    SelfBorrowing { val: i32, reference_to_val: *const i32 },
    Finished,
}

Sobald ein FakeGenerator -Wert fixiert ist, darf der Benutzercode nicht mehr direkt nach Wert ( foo: FakeGenerator ) oder sogar nach Referenz ( foo: &mut FakeGenerator ) darauf zugreifen können, da letzterer dies tun würde Erlaube die Verwendung von swap oder replace . Stattdessen arbeitet der Benutzercode direkt mit z. B. &mut Pinned<FakeGenerator> . Auch dies ist den Regeln für PinMut<'a, FakeGenerator> des bestehenden Vorschlags sehr ähnlich. Als Beispiel für eine bessere Kompositionsfähigkeit kann das vom Compiler generierte Impl das vorhandene Merkmal Iterator , anstatt ein neues Merkmal zu benötigen, das Pin<Self> :

impl Iterator for Pinned<FakeGenerator> {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        /* elided */
    }
}

Auf der anderen Seite können wir für die Erstkonstruktion FakeGenerator -Werte direkt ausgeben und sie verschieben lassen, solange wir garantieren, dass nur nicht selbst ausleihende Zustände zugänglich sind, bevor sie angeheftet werden:

impl FakeGenerator {
    fn new() -> Self { FakeGenerator::Initial }
}

Wenn wir also zwei FakeGenerator nach Wert zusammensetzen möchten, ist die Konstruktion einfach:

struct TwoGenerators {
    a: FakeGenerator,
    b: FakeGenerator,
}

impl TwoGenerators {
    fn new() -> Self {
        TwoGenerators {
            a: FakeGenerator::new(),
            b: FakeGenerator::new(),
        }
    }
}

Dann können wir das TwoGenerators -Objekt als Ganzes anheften:

let generator = pin_box(Box::new(TwoGenerators::new()));

Wie Sie bereits erwähnt haben, benötigen wir Feldprojektionen: einen Weg von &mut Pinned<TwoGenerators> zu &mut Pinned<FakeGenerator> (Zugriff auf entweder a oder b ). Auch hier sieht dies dem vorhandenen Pin -Design sehr ähnlich. Im Moment würden wir ein Makro verwenden, um Accessoren zu generieren:

// Some helper methods:
impl<T> Pinned<T> {
    // Only call this if you can promise not to call swap/replace/etc.:
    unsafe fn unpin_mut(&mut self) -> &mut T {
        &mut self.val
    }
    // Only call this if you promise not to call swap/replace/etc. on the
    // *input*, after the borrow is over.
    unsafe fn with_mut(ptr: &mut T) -> &mut Pinned<T> {
        &mut *(ptr as *mut T as *mut Pinned<T>)
    }
}

// These accessors would be macro-generated:
impl Pinned<TwoGenerators> {
    fn a(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().a) }
    }
    fn b(&mut self) -> &mut Pinned<FakeGenerator> {
        unsafe { Pinned::with_mut(&mut self.unpin_mut().b) }
    }
}

Genau wie das vorhandene Design müsste das Makro jedoch verhindern, dass der Benutzer Drop für TwoGenerators implementiert. Andererseits würde uns der Compiler im Idealfall erlauben, impl Drop for Pinned<TwoGenerators> . Es lehnt dies derzeit mit dem Fehler ab, dass "Implementierungen von Drop nicht spezialisiert werden können", der jedoch geändert werden könnte. IMO wäre das ein bisschen schöner als PinDrop , da wir kein neues Merkmal brauchen würden.

Als zukünftige Erweiterung könnte der Compiler nun im Prinzip die Verwendung der nativen Feldsyntax unterstützen, um von Pinned<Struct> auf Pinned<Field> wechseln, ähnlich dem Vorschlag unter dem vorhandenen Design, dass der Compiler ein natives &pin T hinzufügen könnte

Mein ideales "Endspiel" ist jedoch nicht das, sondern etwas Dramatischeres (und komplexeres zu implementieren), bei dem der Compiler eines Tages "native" unbewegliche Typen unterstützen würde, einschließlich existenzieller Lebensdauern. Etwas wie:

struct SelfReferential {
    foo: i32,
    ref_to_foo: &'foo i32,
}

Diese Typen würden sich wie Pinned<T> verhalten, wenn sie !Sized usw. wären. [1] Im Gegensatz zu Pinned würden sie jedoch keinen anfänglichen beweglichen Zustand erfordern. Stattdessen würden sie auf der Grundlage der "garantierten Kopierentfernung" arbeiten, wobei der Compiler die Verwendung von unbeweglichen Typen in Funktionsrückgabewerten, Strukturliteralen und verschiedenen anderen Stellen erlauben würde, aber garantieren würde, dass sie unter der Haube an Ort und Stelle konstruiert würden. Wir konnten also nicht nur schreiben:

let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };

(was du schon irgendwie machen kannst) ... wir könnten auch Dinge schreiben wie:

fn make_self_referential() -> SelfReferential {
    let sr = SelfReferential { foo: 5, ref_to_foo: &sr.foo };
    sr
}

oder

let sr: Box<SelfReferential> = box SelfReferential { foo: 5, ref_to_foo: &sr.foo };

Das Obige ist natürlich nur eine sehr grobe Skizze, wie das Feature aussehen würde. Die von mir verwendete Syntax weist Probleme auf, und die Syntax ist die geringste der vielen Komplexitäten, die mit dem Entwerfen des Features verbunden sind. (Trotzdem habe ich genug darüber nachgedacht, dass ich ziemlich sicher bin, dass die Komplexität herausgearbeitet werden kann - dass es nicht nur eine inkohärente Idee ist, die nicht funktionieren kann.)

Ich erwähne es nur als Teil der Motivation in der Gegenwart, ein !DynSized -isches Design anstelle des vorhandenen Pin -Designs zu verwenden. Ich denke, &'a Pinned<T> ist bereits besser als Pin<'a, T> weil es eine kombinatorische Explosion von Zeigertypen vermeidet. Aber es erbt einige der gleichen Probleme:

  1. Es werden grundsätzlich keine Typen unterstützt, denen ein beweglicher Anfangszustand fehlt, und
  2. Es ist laut, verwirrend (der Unterschied zwischen angehefteten und nicht angehefteten Verweisen auf denselben Typ ist schwer zu erklären) und macht unbewegliche Typen zweitklassig. Ich möchte, dass sich unbewegliche, selbstreferenzielle Typen erstklassig fühlen - sowohl weil sie von Natur aus nützlich sind, als auch damit Rust für Menschen aus anderen Sprachen besser aussieht, bei denen es einfach und üblich ist, selbstreferenzielle Werte zu erstellen.

Ich stelle mir vor, dass native unbewegliche Typen in Zukunft beide Probleme lösen würden und der meiste Code niemals das Wort "Pin" verwenden müsste. (Obwohl Pinned möglicherweise noch einige Anwendungsfälle hat, für Fälle, in denen die Kopierentscheidung nicht gut genug ist und Sie wirklich einen anfänglichen beweglichen Zustand benötigen.) Im Gegensatz dazu backt Pin das Konzept von a Separaten "noch nicht fixierten" Zustand in das Design jedes Merkmals, das es verwendet. Und ein eingebautes &pin würde es in die Sprache backen.

Wie auch immer, hier ist ein Spielplatz-Link für das Pinned Design oben.

[1] ... obwohl wir vielleicht ein neues Merkmal ReallySized (mit einem weniger albernen Namen) für Typen wollen, die statisch groß sind, aber möglicherweise beweglich sind oder nicht. Die Sache ist, dass es hier sowieso eine gewisse Abwanderung geben wird, da mit der Unterstützung von nicht skalierten Werten viele Funktionen, die derzeit Sized Argumente annehmen, genauso gut mit nicht skalierten, aber beweglichen funktionieren könnten. Wir werden sowohl die Grenzen bestehender Funktionen als auch Empfehlungen ändern, welche Grenzen für zukünftige Funktionen verwendet werden sollen. Ich behaupte, es könnte sich sogar lohnen, wenn eine zukünftige Ausgabe die (implizite) Standardbindung für Funktionsgenerika ändert, obwohl dies einige Nachteile hätte.

[bearbeiten: festes Codebeispiel]

@ RalfJung

Ich sehe die Closure-basierte Stack-Pinning-API als Lösung dafür, mit einem zukünftigen Weg (einmal & pin ist ein Sprachprimitiv) für etwas Ergonomischeres und vom Compiler geprüftes. Es gibt auch diese makrobasierte Lösung, die von @cramertj vorgeschlagen wird.

Ich glaube, der auf Abschluss basierende funktioniert mit async / await nicht richtig, weil Sie nicht innerhalb eines Abschlusses nachgeben können. Das makrobasierte ist jedoch interessant; Wenn das wirklich sicher ist, ist es ziemlich genial. Ich dachte nicht, dass es zuerst funktionieren könnte, weil eine Panik während eines der Tropfen im Zielfernrohr dazu führen könnte, dass die anderen auslaufen, aber anscheinend wurde das mit MIR behoben?

Ich war mir auch nicht sicher über die Interaktion zwischen der Garantie "Destruktoren werden ausgeführt, bevor der Speicher freigegeben wird" und der Fähigkeit, angeheftete Felder zu projizieren. Wenn der Drop der obersten Ebene in Panik geriet, hatte ich gedacht, dass projizierte angeheftete Felder im Wert ihre Drops nicht ausführen würden. Auf dem Rust-Spielplatz scheint es jedoch tatsächlich so zu sein, dass die Felder des Typs auch nach der Panik des Typs der obersten Ebene ohnehin fallen, was ziemlich aufregend ist! Ist diese Garantie tatsächlich irgendwo dokumentiert? Es scheint notwendig zu sein, wenn das Pinning von Stapeln mit Pin-Projektionen funktionieren soll (das oder etwas Schwergewichtigeres wie PinDrop, das bei Panik immer abbricht, was unerwünscht erscheint, da es einen Funktionsunterschied zwischen Drop und PinDrop verursachen würde).

Warum denkst du, dass das Hinzufügen eines impl Unpin für Vecist abwärtskompatibel, aber das Gleiche für Rohzeiger nicht?

Ich verstehe Ihren Standpunkt: Jemand könnte sich auf die automatische Implementierung von !Unpin aus einem eigenen Vec<T> -Feld verlassen und seine eigenen Accessoren implementieren, bei denen angenommen wurde, dass das Fixieren für bestimmte !Unpin T transitiv ist PinMut<Vec<T>> Ihnen keine sichere Möglichkeit bietet, PinMut<T> , könnte unsicherer Code dennoch PinMut::deref ausnutzen, um rohe Zeiger herauszuholen und Annahmen darüber zu treffen Die Zeiger sind stabil. Ich denke, dies ist eine andere Situation, in der es nur dann abwärtskompatibel ist, wenn unsicherer Code nicht davon abhängt, dass !Unpin durch Vec (oder was auch immer) transitiv ist. Diese Art von starkem Vertrauen in negative Argumente scheint mir jedoch sowieso faul zu sein; Wenn Sie sicherstellen möchten, dass Sie !Unpin wo T nicht ist, können Sie immer ein PhantomData<T> hinzufügen (ich denke, dieses Argument gilt auch für Rohzeiger). Eine pauschale Anweisung wie "Es ist UB anzunehmen, dass Typen in der Standardbibliothek sind! Unpin in unsicherem Code, unabhängig von ihren Typparametern, es sei denn, der Typ wird explizit deaktiviert oder seine Dokumentation erklärt, dass darauf vertraut werden kann" würde wahrscheinlich ausreichen.

Das makrobasierte ist jedoch interessant; Wenn das wirklich sicher ist, ist es ziemlich genial. Ich dachte nicht, dass es zuerst funktionieren könnte, weil eine Panik während eines der Tropfen im Zielfernrohr dazu führen könnte, dass die anderen auslaufen, aber anscheinend wurde das mit MIR behoben?

Es wäre ein Fehler in MIR, wenn Panik während des Abwurfs dazu führen würde, dass der Abwurf der verbleibenden lokalen Variablen übersprungen wird. Das sollte einfach von "normaler Tropfen" zu "Abwicklungstropfen" wechseln. Eine weitere Panik bricht das Programm ab.

@ RalfJung

Es wäre ein Fehler in MIR, wenn Panik während des Abwurfs dazu führen würde, dass der Abwurf der verbleibenden lokalen Variablen übersprungen wird. Das sollte einfach von "normaler Tropfen" zu "Abwicklungstropfen" wechseln. Eine weitere Panik bricht das Programm ab.

Werden andere Felder in der Struktur, die gelöscht werden, in diesem Zusammenhang als lokale Variablen betrachtet? Das ist mir aus keiner Dokumentation für Benutzer wirklich klar (tatsächlich gilt dies für die gesamte Garantie, von der Sie sprechen - ich habe nur herausgefunden, dass es sich tatsächlich um einen Fehler handelt, der vom Issue-Tracker behoben werden musste).

@comex

Die Sache mit Pinned (was Sie vorschlagen) ist, dass ich denke, wenn wir es implementieren wollten (sobald Rust alle Funktionen installiert hatte), müssten wir nicht viel mehr tun, um es zu machen abwärtskompatibel mit vorhandenem Code:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

(Ich denke, eine Implementierung von deref von Pin bis Pinned wurde ebenfalls vorgeschlagen). Es ist lehrreich, sich die Stellen anzusehen, an denen dies anscheinend nicht funktioniert. Zum Beispiel:

impl Drop for Pinned<TwoGenerators>

funktioniert nicht (zumindest nicht einfach) mit PinMut . Selbst wenn wir davon ausgehen, dass dies die Drop für TwoGenerators selbst ersetzt, wenn der Typ Pinned wird (ich bin nicht sicher, wie dies funktionieren würde?), Würde Rust dies immer noch nicht tun Sie müssen die Pinned -Version des Konstruktors für alle projizierten Felder aufrufen, da die Felder nur nach Wert gehalten werden. Wenn die angeheftete Version eines Destruktors nur immer verwendet wurde, wenn sie vorhanden war, ist dies praktisch identisch mit PinDrop , nur mit seltsamer Syntax, und ich sehe nicht, wie es besser ist.

Man ist jedoch versucht, die Auswahl eines Destruktors in Betracht zu ziehen, indem man rekursiv analysiert, ob ein Wert auf einem Pinned verwurzelt ist. Beachten Sie, dass wir uns nicht unbedingt darauf verlassen können, dass wir zum Zeitpunkt der Kompilierung entscheiden können, ob der Drop T Pinned<T> oder der Drop T , wenn wir jemals Objekte mit By-Value-Merkmalen zulassen möchten. Ich nehme an, Sie denken, dass es in solchen Fällen einen separaten vtable-Eintrag für die Pinned -Version geben könnte? Diese Idee fasziniert mich irgendwie. Es erfordert definitiv umfangreiche Compiler-Unterstützung (viel mehr als die vorgeschlagenen PinDrop ), aber es könnte in gewisser Hinsicht insgesamt schöner sein.

Beim erneuten Lesen des Threads erinnere ich mich jedoch, dass es andere Probleme gibt: Die Implementierung von deref für angeheftete Typen funktioniert für Pinned<T> wirklich nicht gut (ich vermute, dies liegt an der Implementierung von deref auf PinMut ist in irgendeiner Weise logisch falsch, weshalb es immer wieder Probleme verursacht, aber es ist wirklich schwer zu rechtfertigen, es aufgrund seiner Bequemlichkeit zu verlieren - es sei denn, Sie machen eine ganze Reihe von Typen bedingungslos Unpin sowieso). Insbesondere finde ich das Beispiel RefCell ziemlich beunruhigend, da es bei Vorhandensein von Pinned::deref bedeutet, dass wir das Pinning mit dem vorhandenen Typ nicht einmal dynamisch erzwingen können (ich weiß nicht) wenn Spezialisierung ausreichen würde). Dies deutet weiter darauf hin, dass wir, wenn wir die Implementierung von deref beibehalten, die API-Oberfläche fast genauso oft mit Pinned duplizieren müssen wie mit Pin ; und wenn wir es nicht behalten, wird Pinned<T> erstaunlich schwer zu benutzen. Es scheint auch nicht so, als würde Box<Pinned<T>> funktionieren, wenn wir nicht ?Move getrennt von ?DynSized hinzufügen (wie im Thread ausgeführt).

All dies mag eine gute Idee sein, aber im Kontext des aktuellen Rust scheint mir das Ganze irgendwie unattraktiv zu sein, besonders wenn man bedenkt, dass im Grunde keine Methoden im aktuellen Rust ?Move Grenzen haben würden (was den Mangel bedeutet von einem deref würde wirklich weh tun, es sei denn, der Typ wäre Unpin In diesem Fall bietet Pinned keine Vorteile. Ich vermute, es wird genauer modelliert, was wirklich vor sich geht, indem das Feststecken einer Eigentümerimmobilie gemacht wird, was es wiederum schwieriger macht, mit Ad-hoc-Entscheidungen wie PinMut::deref und für eine viel angenehmere Benutzeroberfläche sorgt (subjektiv sowieso). , aber es fügt eine Menge Sprachmaschinerie hinzu, um dies zu tun, und es hört sich nicht so an, als ob Sie denken, dass irgendetwas davon besonders nützlich ist (im Vergleich zu den einheimischen unbeweglichen Typen, die Sie vorschlagen). Ich weiß auch nicht, ob das, was wir bekommen, dass wir nicht vollständig abwärtskompatibel werden konnten (meistens, je nach Pin-Status eine andere Drop-Implementierung aufzurufen), tatsächlich so nützlich ist, selbst wenn es möglich ist (vielleicht könnten Sie eine speichern Verzweigen Sie manchmal im Destruktor, wenn Sie wissen, dass der Typ fixiert ist?). Ich bin mir also nicht sicher, ob es sich an dieser Stelle lohnt, den Vorschlag für PinMut zu ändern. Aber vielleicht fehlt mir ein wirklich überzeugender konkreter Anwendungsfall.

Die Sache mit Pinned (was Sie vorschlagen) ist, dass ich denke, wenn wir es implementieren wollten (sobald Rust alle Funktionen installiert hatte), müssten wir nicht viel mehr tun, um es zu machen abwärtskompatibel mit vorhandenem Code:

type PinMut<'a, T> = &'a mut Pinned<T>;
type Pin<'a, T> = &'a Pinned<T>;

Zuallererst würde Pinned selbst eine minimale bis gar keine Compilerunterstützung erfordern, wenn Sie das mit "Features" meinen. Wenn Sie Bibliotheksdesign wie DynSized und verwandte Merkmale meinen, dann ist das gültig, aber ...

Was Sie vorgeschlagen haben, wäre nicht wirklich abwärtskompatibel, da Sie beispielsweise versuchen könnten, dasselbe Merkmal sowohl für Pin<'a, T> als auch für &'a T zu implementieren, was plötzlich zu Konflikten führen würde.

Generell gibt es einen signifikanten Unterschied bei API-Designs. Bei Pin müssen für Merkmale, von denen erwartet wird, dass sie von unbeweglichen Typen implementiert werden, die Methoden PinMut<Self> annehmen. Generische Funktionen, die Verweise auf unbewegliche Typen verwenden möchten, müssen wie fn foo<T>(p: PinMut<T>) aussehen. usw. Andererseits vermeidet das Pinned -Design in den meisten Fällen die Notwendigkeit neuer Merkmale, da Sie Merkmale für Pinned<MyStruct> implizieren können. So:

  1. Unbewegliche Typen wären nicht kompatibel mit vorhandenen Merkmalen, deren Methoden &self oder &mut self annehmen: Generatoren könnten beispielsweise Iterator nicht implizieren. Wir würden also eine Reihe neuer Merkmale benötigen, die den vorhandenen entsprechen, aber stattdessen PinMut<Self> . Wenn wir den Kurs ändern und PinMut<T> einem Alias ​​für &mut Pinned<T> , könnten wir zurückgehen und all diese doppelten Merkmale ablehnen, aber das wäre ziemlich dumm. Besser, Sie brauchen die Duplikate gar nicht erst.

  2. Auf der anderen Seite würden neu entworfene oder generatorspezifische Merkmale wahrscheinlich PinMut<Self> als einzige Option annehmen, auf Kosten des Hinzufügens von Rauschen für Typen, die die Merkmale implementieren möchten, aber nicht unbeweglich sind und nicht benötigen festgesteckt werden. (Insbesondere müssten Anrufer PinMut::new anrufen, um von &mut self auf PinMut<Self> , vorausgesetzt Self: Unpin .) Selbst wenn Pin<T> zu einem wird Alias ​​für &mut Pinned<T> , es würde keine Möglichkeit geben, dieses Rauschen loszuwerden. Und die zukünftigen nativen unbeweglichen Typen, die ich mir vorstelle, befinden sich in der gleichen Situation wie bewegliche Typen und müssen unnötigerweise in Pinned eingewickelt werden, wenn sie immer als fixiert betrachtet werden.

Ich werde auf den Rest Ihres Beitrags in einem zweiten Beitrag antworten.

In Bezug auf Drop

Ich bin ein bisschen verwirrt von dem, was Sie über Drop sagen, aber in dem Maße, in dem Sie versuchen, es abwärtskompatibel mit PinMut , werde ich nicht darüber nachdenken weil ich nicht denke, dass es ein guter Ansatz ist.

Ich denke, der beste Ansatz ist, dass Sie zwei Optionen haben, wenn Sie eine Struktur wie TwoGenerators haben:

  1. Kein manuelles Drop impl für TwoGenerators oder Pinned<TwoGenerators> ;
  2. Sie implizieren Drop für Pinned<TwoGenerators> ; In der Zwischenzeit generiert dasselbe Makro, mit dem Sie Accessoren erhalten, ein Drop Impl für TwoGenerators selbst, das sich einfach auf &mut Pinned<TwoGenerators> wirft und dieses fallen lässt. (Dies ist sicher: Die Invariante, die erforderlich ist, um &mut T auf &mut Pinned<T> besteht darin, dass Sie T nach dem Ende der Ausleihe nicht mehr bewegen, und im Fall von Drop , Sie haben die letzte Ausleihe, die jemals für diesen Wert erstellt wurde.)

Der einzige Grund für zwei Optionen besteht darin, dass Sie, wie bereits erwähnt, möglicherweise nicht möchten, dass Ihre Struktur Drop impliziert, da Strukturen, die dies nicht implizieren, vom Ausleihprüfer lockerer behandelt werden.

Ich verstehe nicht, warum Sie einen separaten Destruktor für den Status "Fixiert" und "Nicht fixiert" haben möchten. Sie müssen also keine Streiche mit vtables spielen, um sie zu unterscheiden.

In Bezug auf RefCell

Ich denke nicht, dass Pinned::deref existieren sollte. Die sicheren makrogenerierten Feldzugriffe sollten ausreichen. Ich sehe nicht, wie das "erstaunlich schwer zu bedienen" ist. Es ist etwas weniger schön, als die native Feldsyntax verwenden zu können, aber eines Tages wird dies durch native unbewegliche Strukturen behoben. Wenn es schwierig zu bedienen ist, gilt das gleiche Problem für Pin .

Dies vermeidet das Problem mit RefCell .

vor allem, wenn man bedenkt, dass im aktuellen Rust im Grunde keine Methode ?Move Grenzen haben würde (was bedeutet, dass das Fehlen eines Derefs wirklich weh tun würde [..])

Im Gegenteil, alles, was an ?Sized gebunden ist, ist implizit ?Move .

Dies ist sinnvoll, da Code mit einem ?Sized im Allgemeinen keine Beweglichkeit annehmen kann. Die einzige Ausnahme ist unsicherer Code, der size_of_val aufruft und dann so viele Bytes enthält, weshalb wir den Hack brauchen, bei dem size_of_val für unbewegliche Typen in Panik geraten würde (und zugunsten einer neuen Funktion veraltet wäre) mit einer richtigen Bindung).

Ich bin ein bisschen verwirrt von dem, was Sie über Drop sagen, aber in dem Maße, in dem Sie versuchen, es abwärtskompatibel mit PinMut zu machen, werde ich nicht darüber nachdenken, weil ich nicht denke, dass dies ein guter Ansatz ist .

Ich sagte, wenn etwas nicht abwärtskompatibel mit PinMut , sollte es einen guten Grund dafür geben. Das, was Sie vorschlagen, ist jedoch in jeder Hinsicht funktional identisch mit PinDrop außer dass Sie es auf Pinned<T> implementieren möchten (was im aktuellen Rust nicht funktioniert). Persönlich denke ich, dass die Spezialisierung von Drop einen wirklich zweifelhaften Präzedenzfall darstellt und mit ziemlicher Sicherheit aus Gründen unerwünscht ist, die nichts mit dem Fixieren zu tun haben. Daher halte ich dies nicht für einen intrinsischen Vorteil. Auf jeden Fall denke ich, dass PinDrop größtenteils vom Rest Ihres Vorschlags getrennt werden kann.

Wenn es schwierig zu bedienen ist, gilt das gleiche Problem für Pin.

Sicher, und wenn wir bereit wären, PinMut::deref loszuwerden, würde es auch gut mit Typen wie RefCell komponieren; Der Unterschied besteht darin, dass wir immer noch eine Lösung mit PinMut während wir deref , was mit Pinned nicht zu funktionieren scheint. Wenn wir die Implementierung von deref loswerden würden, würde ich wahrscheinlich eher zustimmen, dass Pinned einen bedeutenden Vorteil bietet.

Aber ich bin mir wirklich nicht sicher, ob ich damit einverstanden bin, dass es in der Praxis nur ein kleines Problem ist, nicht deref : Zum Beispiel bedeutet dies, dass Sie im aktuellen Zustand mit &Pinned<Vec<T>> nichts tun T: !Unpin , und das Gleiche gilt für praktisch jeden vorhandenen Bibliothekstyp. Dies ist ein Problem, das sich aus der Funktionsweise von Unpin ergibt, nicht aus der Art der Referenz, die Sie haben. Das Ökosystem müsste sich gemeinsam dafür entscheiden, Dinge in der Größenordnung von impl Deref for Pinned<Vec<T>> { type Target = Pinned<[T]>; } oder so zu tun, was meiner Meinung nach einem impl PinDeref<Vec<T>> vorzuziehen wäre, wenn es zum Laufen gebracht werden kann, aber das ist in eine Welt ohne deref . In der Welt mit deref können fast alle Bibliotheken ohne pin-bezogene Accessoren davonkommen und haben immer noch eine halbwegs anständige Unterstützung für !Unpin -Typen.

Im Gegenteil, alles, was eine Größenbeschränkung hat, ist implizit Verschieben.

Ah ja, das ist ein guter Punkt. Leider funktioniert eine ganze Reihe von Rust-Code nicht mit Typen mit einer Bindung von !Sized , da dies nicht die Standardeinstellung ist, aber zumindest ein Teil davon. Ich denke nicht, dass dies ein zwingender Vorteil ist, da die meisten Dinge, die ich mit nicht dimensionierten Werten tun kann, darin bestehen, Methoden mit & oder &mut aufzurufen (z. B. für Slices oder Merkmalsobjekte) was ich unter Ihrem Vorschlag tun könnte (mit Ausnahme von Unpin Typen), da Sie nicht Pinned::deref . Vielleicht könnten die häufigsten Fälle behoben werden, indem #[derive] Implementierungen auch unterschiedliche Instanzen für Pinned<T> oder so generieren?

Drop

Persönlich denke ich, dass die Spezialisierung von Drop einen wirklich zweifelhaften Präzedenzfall darstellt und mit ziemlicher Sicherheit aus Gründen unerwünscht ist, die nichts mit Pinning zu tun haben. Daher halte ich dies nicht für einen intrinsischen Vorteil. Auf jeden Fall denke ich, dass PinDrop größtenteils vom Rest Ihres Vorschlags getrennt werden kann.

Ich bin damit einverstanden, dass dies trennbar ist, aber ich denke nicht, dass es zweifelhaft ist. Zumindest ... was Sie gesagt haben, ist richtig, es ist eine Form der Spezialisierung. Es ist nicht buchstäblich eine Spezialisierung eines übergeordneten Blanket-Impls von Drop , aber der vom Compiler generierte Tropfenkleber entspricht der Spezialisierung, indem er Drop nur aufruft, wenn er implementiert ist. Eine 'Userland'-Implementierung würde folgendermaßen aussehen (ohne die Tatsache zu berücksichtigen, dass Sie drop nicht manuell aufrufen können):

trait DropIfImplemented {
    fn maybe_drop(&mut self);
}
impl<T: ?Sized> DropIfImplemented for T {
    default fn maybe_drop(&mut self) {}
}
impl<T: ?Sized + Drop> DropIfImplemented for T {
    fn maybe_drop(&mut self) { self.drop() }
}

Ich stelle mir also vor, dass der Grund, warum Sie derzeit keine 'spezialisierten' Drop-Impls schreiben können, der gleiche Grund ist, warum die Spezialisierung selbst derzeit nicht stichhaltig ist: Inkohärenz zwischen trans (wodurch Lebensdauerparameter gelöscht werden) und typeck (was nicht). Mit anderen Worten, wenn wir beispielsweise impl Drop for Foo<'static> schreiben könnten, würde dies tatsächlich für jedes Foo<'a> aufgerufen, nicht nur für Foo<'static> , da Codegen davon ausgeht, dass die beiden Typen identisch sind.

Die gute Nachricht ist, wie Sie wahrscheinlich wissen, dass versucht wurde, einen Weg zu finden, um spezialisierte Geräte so einzuschränken, dass sie diese Art von Inkohärenz nicht erzeugen können. Und es wird erwartet, dass die Spezialisierung irgendwann mit einer solchen Grenze ausgeliefert wird. Sobald dies geschieht, sehe ich keinen Grund, warum wir nicht dieselben Regeln auf Drop Impls anwenden können - und um die Sprache so konsistent wie möglich zu gestalten, sollten wir dies tun.

Jetzt wollen wir das Feststecken bei der Spezialisierung nicht blockieren. Ich behaupte jedoch, dass das Zulassen von impl Drop for Pinned<MyStruct> - oder allgemeiner das Zulassen von impl<params> Drop for Pinned<MyStruct<params>> unter den gleichen Bedingungen, die der Compiler derzeit impl<params> Drop for MyStruct<params> zulässt - garantiert eine Teilmenge der Spezialisierung ist erlauben, also wenn wir heute einen Sonderfall dafür gemacht haben, wird es irgendwann in einer allgemeineren Regel verschwinden.

Aber auch dies ist trennbar; Wenn die Leute das nicht mögen, könnten wir stattdessen ein separates Merkmal haben.

Unpin

Aber ich bin mir wirklich nicht sicher, ob ich damit einverstanden bin, dass es in der Praxis nur ein kleines Problem ist, nicht deref : Zum Beispiel bedeutet dies, dass Sie im aktuellen Zustand mit &Pinned<Vec<T>> nichts tun können, wobei T: !Unpin , und das Gleiche gilt für praktisch jeden vorhandenen Bibliothekstyp. Dies ist ein Problem, das sich aus der Funktionsweise von Unpin ergibt, nicht aus der Art der Referenz, die Sie haben.

Äh ... okay, lass mich meine Aussage korrigieren. Pinned::deref sollte existieren, aber es sollte an Unpin gebunden sein - obwohl ich es Move .

Der einzige Grund, warum das Deref impl für PinMut Probleme mit RefCell ist, dass es (im Gegensatz zum DerefMut impl) nicht an Unpin gebunden ist . Und der Grund, die Bindung nicht zu haben, ist der Wunsch, Benutzern zu ermöglichen, &MyImmovableType , wodurch unbewegliche Typen Merkmale mit Methoden implizieren können, die &self benötigen, und an generische Funktionen übergeben werden können, die &T benötigen. &mut self grundsätzlich unmöglich, funktioniert aber meistens mit &self da Sie mit mem::swap oder mem::replace nicht aussteigen können RefCell . Die Kompatibilität mit vorhandenen Referenzen ist jedoch so wertvoll, dass sie unterstützt werden sollte, selbst wenn sich die Beschränkung auf unveränderliche Referenzen willkürlich anfühlt, selbst wenn sie zu Kludges führt.

Mit Pinned können wir sowohl unveränderliche als auch veränderbare Referenzen unterstützen: Sie implizieren Ihre Eigenschaften nur auf Pinned<MyStruct> und nicht direkt auf MyStruct . Der Nachteil ist, dass es nicht mit Merkmalen oder Funktionen kompatibel ist, die &T aber separat eine Self: Sized Bindung haben; aber diese sind relativ selten und oft unbeabsichtigt.

Interessanterweise erfordert Pinned selbst nicht wirklich, dass Unpin existiert. Warum sollte jemand eigentlich ein &Pinned<Vec<T>> erstellen? Mit PinMut würden verschiedene Merkmale PinMut<Self> einnehmen, sodass selbst Implikationen dieser Merkmale für bewegliche Typen ein PinMut erhalten müssten. Mit Pinned , wie ich bereits sagte, würden Merkmale weiterhin &self oder &mut self einnehmen, und Sie würden sie für Pinned<MyStruct> implizieren. Wenn Sie das gleiche Merkmal für Vec<T> implizieren möchten, muss Pinned nicht ins Bild kommen.

Eine mögliche Quelle wäre jedoch das Feldzugriffsmakro. Wenn Sie haben

struct SomePinnable {
    gen: FakeGenerator,
    also_a_vec: Vec<Foo>,
}

dann würde das einfachste Design immer Accessoren mit Pinned generieren:

impl Pinned<SomePinnable> {
    fn gen(&self) -> &Pinned<FakeGenerator> { … }
    fn gen_mut(&mut self) -> &mut Pinned<FakeGenerator> { … }
    fn also_a_vec(&self) -> &Pinned<Vec<Foo>> { … }
    fn also_a_vec_mut(&mut self) -> &mut Pinned<Vec<Foo>> { … }
}

… Und es liegt an Ihnen, mit den Pinned umzugehen, wenn Sie es nicht wollten. Tatsächlich denke ich, dass dies in Ordnung ist, da Unpin / Move tatsächlich existieren sollte, siehe unten. Wenn es das nicht gäbe, wäre eine Alternative die Möglichkeit, sich pro Feld anzumelden, um eine direkte Referenz zu erhalten, anstatt eine Pinned 1. Das heißt, Sie hätten

    fn also_a_vec(&self) -> &Vec<Foo> { … }
    fn also_a_vec_mut(&mut self) -> &mut Vec<Foo> { … }

Es wäre nicht sinnvoll, sowohl Pinned als auch Nicht- Pinned Accessoren zu haben, aber beides sollte für sich allein in Ordnung sein.

… Aber ja, wir müssen ein Move Merkmal haben, nur nicht für Pinned . Zum Beispiel wäre es Teil der Bindung für die neue Version von size_of_val (Korrektur: Dies wäre nicht der Fall, aber es wird erwartet, dass unsicherer Code dies überprüft, bevor versucht wird, beliebige Typen basierend auf dem Ergebnis zu speichern von size_of_val ); In Zukunft wird es mit nicht dimensionierten Werten die Grenze (entspannt von Sized ) für ptr::read und mem::swap und mem::replace . Wenn wir jemals &move , wäre es die Grenze, um Sie aus einem herausziehen zu lassen. und etwas Ähnliches gilt für die garantierte Kopierentscheidung.

Solange wir das Merkmal haben, gibt es keinen Grund, Pinned::deref (und deref_mut ) nicht mit einem T: Move gebunden zu haben.

[edit: wie mich pythonesque erinnerte, hat dies eine andere Semantik als Unpin , also egal.]

(Und dann möchten Typen wie Vec und Box Move manuell implementieren, damit sie unabhängig davon gelten, ob der Elementtyp Move .)

Äh ... okay, lass mich meine Aussage korrigieren. Pinned :: deref sollte existieren, aber es sollte auf Unpin beschränkt sein - obwohl ich es Move nennen werde.

Okay, warte. Ist das ?Move oder Move ? Ersteres würde bedeuten, dass !Unpin Typen in vielen Fällen nicht einmal konstruiert werden können; Letzteres lässt mich fragen, wie genau wir uns auf Typen wie Pinned<T> beziehen (da ?DynSized nicht wirklich die richtige Grenze für sie ist). Ich hoffe auf jeden Fall, dass sie nicht ein und dasselbe sind - ansonsten macht die Behandlung von Move als Unpin genau das, was wir vermeiden wollen, und macht den Typ in dem Moment unbeweglich, in dem er konstruiert wird.

Der Nachteil ist, dass es nicht mit Merkmalen oder Funktionen kompatibel ist, die & T annehmen, sondern separat eine Self: Sized-Bindung haben; aber diese sind relativ selten und oft unbeabsichtigt.

Es gibt einen viel bedeutenderen praktischen Nachteil, nämlich dass nur wenige dieser Merkmale oder Funktionen tatsächlich mit & Pinned funktionierenheute. Sie könnten dazu gebracht werden, damit zu arbeiten, aber es würde eine große Anzahl zusätzlicher Trait-Implementierungen und (wie gesagt) wahrscheinlich eine bedeutende Überarbeitung bestehender #[derive] -Implementierungen erfordern. Dies ist auch ein Preis, der auch für neue Dinge bezahlt werden müsste - Sie müssten alles für &Pinned<Self> implementieren, wenn Sie möchten, dass es für !Unpin Typen funktioniert. Dies ist eine (viel) bessere Situation für Merkmale, die &mut self als bei PinMut , aber schlechter für &self , was (ich vermute) viel häufiger vorkommt. Daher denke ich, dass dies die korrektere Lösung ist (in dem Sinne, dass die Pinned -Version besser wäre, wenn wir nicht viele vorhandene Rust-Bibliotheken hätten), aber möglicherweise nicht die brauchbarere.

Mit Pinned würden Eigenschaften, wie ich bereits sagte, weiterhin & self oder & mut self annehmen, und Sie würden sie für Pinned implizieren

Die Neuimplementierung aller Merkmale in der API-Oberfläche von Vec , diesmal nur mit Pinned , klingt für mich nicht so gut (zumal einige der Merkmale nicht einmal damit funktionieren). Ich bin mir ziemlich sicher, dass ich entweder Deref von Fall zu Fall selektiv implementieren kann (z. B. &Pinned<Vec<T>> auf &[Pinned<T>] gehen lassen) oder einfach das gesamte Vec be Unpin (und keine Pin-Projektion zulassen) ist viel vernünftiger. In jedem Fall sind beide mehr Arbeit als absolut nichts zu tun, und es müsste über eine ganze Reihe vorhandener Typen und Merkmale repliziert werden, damit unbewegliches Material mit ihnen funktioniert.

Ich könnte davon überzeugt sein - ich mag die Pinned -Lösung umso mehr, je mehr ich darüber nachdenke -, aber ich weiß einfach nicht, wo all diese neuen Trait-Implementierungen auf Pinned<T> tatsächlich sind werde kommen von; Es scheint mir wahrscheinlicher, dass die Leute sich einfach nicht die Mühe machen, sie umzusetzen.

Interessanterweise erfordert Pinned selbst nicht, dass Unpin überhaupt existiert. Warum sollte eigentlich jemand ein & Pinned erstellen?>?

Es gibt ziemlich gute Gründe, dies zu tun (z. B.: Ihr angehefteter Typ enthält Vec ). Aufdringliche Sammlungen werden sehr häufig auf diese Art von Szenario stoßen. Ich denke, dass jeder Vorschlag, der auf der Idee basiert, dass die Leute niemals Pinned Verweise auf vorhandene Container wollen oder dass Sie sich dafür entscheiden müssen, dass Unpin funktioniert, wahrscheinlich nicht gut funktioniert. Es wäre unglaublich störend, wenn man sich nicht grundsätzlich für das reguläre Ökosystem von Rust entscheiden könnte, indem man eine Unpin -Bindung hinzufügt (tatsächlich würde fast jeder Anwendungsfall, den ich für unbewegliche Typen habe, erheblich schwieriger werden).

Mit PinMut würden verschiedene Eigenschaften PinMut annehmenDaher müssten auch Impls dieser Merkmale für bewegliche Typen einen PinMut erhalten.

Sicher! Der große Vorteil der Pinned -Version besteht darin, dass Sie keine eindeutigen Merkmale für veränderbare angeheftete Referenzen benötigen. Es ist jedoch schlechter oder neutral als PinMut mit deref für fast jedes andere Szenario.

Es wäre nicht sinnvoll, sowohl angeheftete als auch nicht angeheftete Accessoren zu haben, aber beides sollte für sich allein in Ordnung sein.

Manuelle Accessoren, die unsicheren Code benötigen, um den Sound zu implementieren, sind für mich eine schlechte Idee. Ich sehe nicht ein, wie ein solcher Vorschlag die Sicherheit der Accessor-Generierung gewährleisten würde (wie verhindern Sie, dass jemand nicht angeheftete Accessoren bereitstellt, ohne dass er unsafe schreibt, um zu behaupten, dass er dies nicht tut?). Wie Sie jedoch bemerken, funktioniert die Verwendung von Move (vorausgesetzt, dies bedeutet tatsächlich Unpin ) einwandfrei.

Solange wir das Merkmal haben, gibt es keinen Grund, Pinned :: deref (und deref_mut) nicht mit einem T: Move gebunden zu haben.

Sicher. Ich spreche hier speziell von !Unpin Typen. Unpin Typen haben auch kein Kompositionsproblem mit PinMut , daher sind sie aus meiner Sicht nicht so relevant. Unpin (oder Move ) Grenzen für Generika sind jedoch nicht angenehm und idealerweise sollten Sie sie nach Möglichkeit vermeiden können. Nochmals, wie ich bereits sagte: Unpin und was auch immer von !Sized impliziert wird, sind nicht gleich und ich denke nicht, dass Sie sie als gleich behandeln können.

… Aber ja, wir brauchen ein Move-Merkmal, nur nicht für Pinned. Zum Beispiel wäre es Teil der Grenze für die neue Version von size_of_val; In Zukunft wird es mit nicht dimensionierten Werten die Grenze (entspannt von Größe) für ptr :: read und mem :: swap und mem :: replace sein. Wenn wir jemals kommen und uns bewegen, wäre es die Grenze, um Sie aus einem herausziehen zu lassen. und etwas Ähnliches gilt für die garantierte Kopierentscheidung.

Ich denke, dies verschmilzt wieder einmal !Unpin (was besagt, dass ein Typ eine nicht standardmäßige Pinning-Invariante haben kann) und die !DynSized -ähnlichen !Move ; Sie können nicht wirklich gleich sein, ohne das unerwünschte Einfrierverhalten zu verursachen.

Ups, du hast vollkommen recht. Unpin kann nicht mit Move identisch sein.

Dann denke ich, ich bin zurück zu der Überzeugung, dass Unpin und damit Pinned::deref überhaupt nicht existieren sollten, und stattdessen sollten wir Situationen vermeiden (wie beim Accessor-generierenden Makro), in denen Sie dies tun würden Holen Sie sich einen Typ wie &Pinned<MovableType> . Aber vielleicht gibt es ein Argument, dass es als separates Merkmal existieren sollte.

Die Neuimplementierung aller Merkmale in der API-Oberfläche von Vec , diesmal nur mit Pinned , klingt für mich nicht so gut (zumal einige der Merkmale nicht einmal damit funktionieren). Ich bin mir ziemlich sicher, dass ich entweder Deref von Fall zu Fall selektiv implementieren kann (z. B. &Pinned<Vec<T>> auf &[Pinned<T>] gehen lassen) oder einfach das gesamte Vec sei Unpin (und keine Pin-Projektion erlaubt), ist viel vernünftiger.

Ja, ich wollte definitiv nicht vorschlagen, die gesamte API-Oberfläche von Vec oder ähnliches neu zu implementieren.

Ich bin damit einverstanden, dass es eine gute Idee wäre, die "Pin-Projektion" auf Vec nicht zuzulassen, da &Pinned<Vec<T>> wie eine fremde Invariante erscheint - Sie sollten die Vec ohne verschieben dürfen Ungültigmachen der PIN des Inhalts.

Wie wäre es alternativ damit, die Umwandlung von Vec<T> in Vec<Pinned<T>> zuzulassen, die den größten Teil der Vec API enthalten würde, aber die Methoden weglässt, die eine Neuzuweisung verursachen können? Das heißt, ändern Sie die Definition von Vec von den aktuellen struct Vec<T> in struct Vec<T: ?Sized + ActuallySized> für einen weniger albernen Namen, wobei Sized Grunde ein Alias ​​für ActuallySized + Move ; Fügen Sie dann ein Sized , das an die Methoden gebunden ist, die eine Neuzuweisung verursachen können, und eine Methode, um die Umwandlung durchzuführen.

Es wäre auch notwendig, die Grenze für den Elementtyp des eingebauten Slice-Typs von Sized in ActuallySized zu ändern. Wenn ich darüber nachdenke, erinnere ich mich an die Unbeholfenheit, Sized zu ändern, dass es etwas anderes als Sized , aber andererseits ist es immer noch wahr, dass die überwiegende Mehrheit von Sized Grenzen hat In vorhandenem Code ist von Natur aus Move erforderlich. Die Notwendigkeit, size_of::<T>() zu kennen, um in ein Slice zu indizieren, ist eine Ausnahme…

Sicher! Der große Vorteil der Pinned -Version besteht darin, dass Sie keine eindeutigen Merkmale für veränderbare angeheftete Referenzen benötigen. Es ist jedoch schlechter oder neutral als PinMut mit deref für fast jedes andere Szenario.

Es hat auch den Vorteil, Konflikte mit RefCell vermeiden, allerdings auf Kosten von Konflikten mit anderen Dingen ( Sized Grenzen).

Manuelle Accessoren, die unsicheren Code benötigen, um den Sound zu implementieren, sind für mich eine schlechte Idee. Ich sehe nicht ein, wie ein solcher Vorschlag die Sicherheit der Accessor-Generierung gewährleisten würde (wie können Sie verhindern, dass jemand nicht angeheftete Accessoren bereitstellt, ohne dass er unsicher schreibt, um zu behaupten, dass er dies nicht tut?).

Weil die Accessoren auf Pinned<MyStruct> impliziert sind, nicht auf MyStruct direkt. Wenn Sie &mut MyStruct , können Sie jederzeit manuell auf ein Feld zugreifen, um &mut MyField , aber Sie befinden sich immer noch in einem beweglichen Zustand. Wenn Sie &mut Pinned<MyStruct> , können Sie &mut MyStruct (vorausgesetzt, entweder MyStruct ist !Unpin oder Unpin existiert nicht). Sie müssen also einen Accessor verwenden, um an die Felder zu gelangen. Der Accessor nimmt &mut Pinned<MyStruct> (dh er nimmt &mut self und ist auf Pinned<MyStruct> impliziert) und gibt Ihnen entweder &mut Pinned<MyField> oder &mut MyField . abhängig davon, welche Option Sie gewählt haben. Sie können jedoch nur den einen oder anderen Accessor-Typ haben, da die kritische Invariante darin besteht, dass Sie nicht in der Lage sein müssen, einen &mut Pinned<MyField> , darauf zu schreiben, den Kredit freizugeben und dann einen &mut MyField (und verschieben).

Es gibt ziemlich gute Gründe, dies zu tun (z. B.: Ihr angehefteter Typ enthält ein Vec ). Aufdringliche Sammlungen werden sehr häufig auf diese Art von Szenario stoßen. Ich denke, dass jeder Vorschlag, der auf der Idee basiert, dass die Leute niemals Pinned Verweise auf vorhandene Container wollen oder dass Sie sich dafür entscheiden müssen, dass Unpin funktioniert, wahrscheinlich nicht gut funktioniert. Es wäre unglaublich störend, wenn man sich nicht grundsätzlich für das reguläre Ökosystem von Rust entscheiden könnte, indem man eine Unpin -Bindung hinzufügt (tatsächlich würde fast jeder Anwendungsfall, den ich für unbewegliche Typen habe, erheblich schwieriger werden).

Ich verstehe nicht ganz, was Sie hier meinen, aber das kann daran liegen, dass Sie auf meinen eigenen Fehler in Bezug auf Unpin gegenüber Move reagiert haben :)

Jetzt, wo ich korrigiert bin ... Wenn Unpin existiert, sollte Vec implizieren. Angenommen, es existiert nicht. Auf welche Szenarien beziehen Sie sich genau?

Für eine Struktur, deren eines Feld Vec , habe ich oben erklärt, wie Sie einen nicht angehefteten Verweis auf das Feld erhalten würden (auf Kosten der Tatsache, dass Sie keinen angehefteten Verweis darauf erhalten können) ist gut).

Ich denke, dies wäre problematisch, wenn Sie eine generische Struktur mit einem Feld möchten, das je nach Typparameter möglicherweise einen Vec oder einen unbeweglichen Typ enthält. Es könnte jedoch einen anderen Weg geben, dies zu lösen, ohne ein Unpin Merkmal zu benötigen, über dessen Implementierung alles nachdenken muss.

@comex

Als Alternative können Sie die Umwandlung von Vec zulassenzu Vec>, welche hätten den größten Teil der Vec-API, lassen jedoch die Methoden weg, die eine Neuzuweisung verursachen können?

Weil...

Ändern Sie also die Definition von Vec gegenüber der aktuellen Struktur VecVec zu strukturierenfür einen weniger albernen Namen, bei dem Sized im Grunde genommen zu einem Alias ​​für ActuallySized + Move wird; Fügen Sie dann eine Größenbindung zu den Methoden hinzu, die eine Neuzuweisung verursachen können, und eine Methode, um die Umwandlung durchzuführen.

... das klingt wirklich sehr, sehr kompliziert und macht nicht das, was Sie wollen (was bedeutet, dass Sie regelmäßig Vec<T> aus &mut Pinned<Vec<T>> oder was auch immer erhalten). Es ist irgendwie cool, dass Sie einen Vektor nachträglich anheften können, sodass Sie ein nettes Analogon zu Box<Pinned<T>> , aber das ist ein orthogonales Problem. Dies ist nur ein weiteres Beispiel dafür, dass das Feststecken als Eigentum des eigenen Typs wahrscheinlich richtig ist. Ich denke, alles an Unpin hat fast nichts mit der Frage zu tun, wie der Referenztyp aufgebaut ist.

Jetzt, wo ich korrigiert bin ... Wenn Unpin existiert, sollte Vec es implizieren. Angenommen, es existiert nicht. Auf welche Szenarien beziehen Sie sich genau?

Ich werde nur darauf antworten, weil ich denke, dass dies meinen Standpunkt verdeutlichen wird: Ich kann nicht einmal ein i32-Feld durch ein PinMut ohne Unpin mutieren. Wenn Sie irgendwann etwas mit Ihrer Struktur anfangen möchten, müssen Sie häufig etwas darin verschieben (es sei denn, es ist vollständig unveränderlich). Es scheint wirklich ärgerlich zu sein, dass Benutzer Feldzugriffsgeräte explizit auf Pinned<MyType> implementieren müssen, insbesondere wenn das betreffende Feld immer sicher verschoben werden kann. Dieser Ansatz scheint auch sehr verwirrend zu sein, wenn er mit einem eingebauten Pinned -Typ verwendet wird, da die rechtlichen Projektionen je nach Feld in einer Weise variieren würden, die nicht vom Feldtyp abhängt, was bereits in Rust abgelehnt wurde Wenn mut Felder entfernt wurden (und IMO, wenn wir eine solche Anmerkung hinzufügen wollen, ist unsafe eine bessere Wahl, da unsichere Felder in der Praxis eine große Fußwaffe sind). Da eingebaute angeheftete Typen nur die einzige Möglichkeit sind, angeheftete Aufzählungen benutzerfreundlich zu gestalten, ist es mir wichtig, dass sie in gewisser Weise mit dem Rest der Sprache übereinstimmen.

Aber noch wichtiger...

Ich denke, dies wäre problematisch, wenn Sie eine generische Struktur mit einem Feld möchten, das je nach Typparameter einen Vec oder einen unbeweglichen Typ enthalten könnte

Das ist so ziemlich der Killer-Anwendungsfall für Unpin , und (für mich) die Tatsache, dass es tatsächlich zu funktionieren scheint, ist ziemlich fantastisch und bestätigt das gesamte Pinning-Modell (bis zu dem Punkt, an dem ich denke, selbst wenn wir anfangen würden vor Rust 1.0 würden wir wahrscheinlich Unpin behalten wollen, wie es ist). Generische Parameter, die in der Struktur inline leben, sind auch so ziemlich das einzige Mal, dass Sie bei den aktuellen Plänen an Unpin gebunden sein müssen (damit fast alle sicheren referenzähnlichen Typen Unpin bedingungslos implementieren). durchgehen.

Aber was ich wirklich nicht bekomme ist: Warum willst du Unpin entfernen? Wenn Sie es eliminieren, erhalten Sie im Wesentlichen nichts. all die schönen Dinge, die man aus Pinned über PinMut (oder umgekehrt) herausholt, haben so ziemlich nichts mit seiner Anwesenheit zu tun. Wenn Sie es hinzufügen, erhalten Sie eine einfache Kompatibilität mit dem Rest des Rust-Ökosystems. FWIW, Unpin und die bedingungslose Implementierung von deref auf Pin sind ebenfalls ziemlich unabhängig voneinander, außer dass sich alle Typen ohne Unpin fühlen Schmerz , dass !Unpin Arten tun (was bedeutet es wahrscheinlich die bedingungslose machen würde deref Umsetzung nützlicher). Ich kann nicht anders, als das Gefühl zu haben, dass mir etwas fehlt.

Werden andere Felder in der Struktur, die gelöscht werden, in diesem Zusammenhang als lokale Variablen betrachtet? Das ist mir aus keiner benutzerbezogenen Dokumentation klar

Fairerweise habe ich https://github.com/rust-lang/rust/issues/50765 geöffnet, um dies zu verfolgen.


@pythonesque

Insbesondere finde ich das RefCell-Beispiel ziemlich beunruhigend, da es in Gegenwart von Pinned :: deref bedeutet, dass wir das Pinning mit dem vorhandenen Typ nicht einmal dynamisch erzwingen können (ich weiß nicht, ob eine Spezialisierung ausreichen würde). Dies deutet weiter darauf hin, dass wir, wenn wir die Deref-Implementierung beibehalten, die API-Oberfläche mit Pinned fast genauso duplizieren müssen wie mit Pin. und wenn wir es nicht behalten, festgestecktwird erstaunlich schwer zu bedienen.

Die Lösung für RefCell besteht darin, zusätzliche Methoden borrow_pin und borrow_pin_mut (unter Verwendung von Pin<RefCell<T>> ) bereitzustellen und den fixierten Zustand des Inneren des RefCell verfolgen PinMut als auch für Pinned funktionieren. Ist Ihr Argument hier also, dass Pinned nicht hilft? Es sollte die Dinge auch für RefCell nicht noch schlimmer machen.

Aber dann schreibst du

Der Unterschied besteht darin, dass wir mit PinMut immer noch eine Lösung zusammenschustern können, während wir deref unterstützen, was mit Pinned anscheinend nicht funktioniert

und ich weiß nicht, worauf Sie sich beziehen, warum sollte dies nicht mit Pinned funktionieren?

@comex

Ich denke nicht, dass Pinned :: deref existieren sollte. Die sicheren makrogenerierten Feldzugriffe sollten ausreichen. Ich sehe nicht, wie das "erstaunlich schwer zu bedienen" ist.

Ich sehe keine Verbindung zwischen den deref und den Feldzugängern. Aber ich sehe auch nicht, wie das deref mit Pinned<T> problematischer wird, also werde ich wohl zuerst auf eine Antwort auf meine obige Frage warten.

Genau wie bei @pythonesque denke ich, dass das Verfolgen des

Wenn wir uns bewusst für den Ansatz entscheiden, den wir für weniger "grundlegend korrekt" halten, sollten wir uns zumindest viel Zeit für Experimente lassen, bevor wir ihn stabilisieren, damit wir so sicher wie möglich sein können, dass wir es nicht sind Ich werde es am Ende bereuen.

@pythonesque , wow, vielen Dank für die ausführliche Zusammenfassung! Freut mich zu hören, dass ein RFC in Arbeit ist. :) :)

Es ist sicher, eine Funktion aufzurufen, die einen veränderlichen Verweis auf einen angehefteten Wert verwendet, solange diese Funktion nicht so etwas wie mem::swap oder mem::replace . Aus diesem Grund ist es natürlicher, wenn diese Funktionen die Unpin -Bindung verwenden, als wenn jeder veränderbare Deref eines Pin zu einem Unpin -Wert unsicher ist.

Wenn eine Funktion später aktualisiert würde, um swap , wäre es nicht mehr sicher, sie bei einem veränderlichen Verweis auf einen angehefteten Wert aufzurufen. Wenn swap und replace diese Grenze haben, hat die aktualisierte Funktion ebenfalls damit zu tun, was deutlich macht, dass dies keine abwärtskompatible Änderung ist.

Also einige Gedanken, die ich hatte:

  1. Grundsätzlich bietet Drop das gleiche Privileg wie Unpin - Sie können &mut für etwas erhalten, das zuvor in einem PinMut . Und Drop ist sicher, was bedeutet, dass Unpin sicher sein sollte (dies ist mem::forget und die Leckpokalypse erneut).
  2. Das ist schön, denn es bedeutet, dass Dinge wie die aktuellen Futures-basierten APIs, die keine Unpin-Generatoren verarbeiten, alle 100% sicher implementiert werden können, selbst wenn sie self: PinMut<Self> (keine unsafe impl Unpin ) benötigen.
  3. Ist die API einwandfrei, wenn Unpin sicher ist? Die Antwort lautet ja: Solange Generatoren Unpin nicht implementieren und es nicht sicher ist, ein Projekt einem !Unpin -Typ zuzuordnen, ist alles sicher.
  4. Dies bedeutet jedoch, dass Stiftprojektionen unsicher sind! Nicht ideal.
  5. Pin-Projektionen sind sicher, wenn Self Unpin oder Drop nicht implementiert. [Bearbeiten: wahr oder falsch?] Können wir diese Überprüfung automatisieren?

Ich habe einige Ideen für eine Sprachgestützte Alternative zu dieser Bibliothek - API, die beinhalten dropping Unpin vollständig und stattdessen die Polarität Spiegeln - ein Pin Charakterzug , dass Sie sich entscheiden , in diese Garantien bekommen, statt Opting- out . Dies würde jedoch eine erhebliche Sprachunterstützung erfordern, während die aktuelle Implementierung vollständig bibliotheksbasiert ist. Ich werde einen weiteren Beitrag schreiben, nachdem ich mehr darüber nachgedacht habe.


ein weiterer Hinweis, weil ich immer wieder vergesse:

Die Sicherheit der Pin-Projektion hängt nur vom Self-Typ ab, nicht vom Feldtyp, da der Feldtyp die Sicherheit seiner öffentlichen API gewährleisten muss, die unbedingt erforderlich ist. Es ist also keine rekursive Prüfung - wenn Self niemals etwas aus seinen Pin herausbewegt, ist die Pin-Projektion auf ein Feld eines beliebigen Typs sicher.

@withoutboats FWIW entspricht genau den Schlussfolgerungen, zu denen @cramertj und ich in unserer vorherigen Diskussionsrunde gelangt sind. Und ich glaube, wir können die gegenseitige Ausschlussprüfung automatisieren, zunächst durch einige speziell entwickelte Attribute, die von der Ableitung ausgegeben werden.

@ohne Boote

Grundsätzlich bietet Drop das gleiche Privileg wie Unpin - Sie können ein & mut für etwas erhalten, das zuvor in einem PinMut enthalten war. Und Drop ist sicher, was bedeutet, dass Unpin sicher sein sollte (dies ist mem :: Vergiss und Leckpokalypse noch einmal).

Ich sehe keinen Zusammenhang mit der Leckpokalypse, stimme aber anders zu. Der einzige Grund, warum ich etwas zögerlich bin (war?), Ist, dass es sich für mich eher wie ein Eckfall anfühlte, um den sich nicht viele Menschen kümmern müssen, solange es nur Drop ist. Ich bin mir nicht sicher, ob das ein Vorteil ist oder nicht. Und außerdem erhöht ein sicheres Unpin nicht nur die Konsistenz hier und "löst" das Drop -Problem, indem es nicht länger zu einem Sonderfall wird (stattdessen können wir uns jedes impl Drop als vorstellen Kommen mit einem impliziten impl Unpin ); Nach dem, was Sie sagen, ist es auch einfacher, es auf der Future -Seite zu verwenden. Es scheint also ein Gesamtsieg zu sein.

@pythonesque, es sei denn, ich vermisse etwas, sicher Unpin verursacht auch keine neuen Probleme für aufdringliche Sammlungen, oder? Wenn sie trotz sicherer Drop , sollten sie trotzdem arbeiten.


@ohne Boote

Pin-Projektionen sind sicher, wenn Self Unpin oder Drop nicht implementiert. [Bearbeiten: wahr oder falsch?] Können wir diese Überprüfung automatisieren?

Sie haben hier zunächst auch den Feldtyp erwähnt, und ich wollte gerade fragen, warum Sie dies für relevant halten. Jetzt sehe ich, dass Sie den Beitrag bearbeiten. :) Ich bin damit einverstanden, dass es sich nur um den Typ Self . Im Folgenden wiederhole ich Ihre Argumentation in meinen Begriffen.

Im Wesentlichen lautet die Frage: Wie wählt Self seine Pinning-Invariante aus? Standardmäßig gehen wir davon aus (auch wenn unsicherer Code vorhanden ist!), Dass die Pinning-Invariante genau mit der eigenen Invariante identisch ist, dh die Invariante ist unabhängig vom Speicherort und dieser Typ führt kein Pinning durch. Solange ich mit einem PinMut<T> nichts anderes machen kann, als daraus ein &mut T , ist das eine sichere Annahme.

Um Feldprojektionen zu aktivieren, sollte die Fixierungsinvariante stattdessen "Alle meine Felder werden an der Invariante ihres jeweiligen Typs fixiert" sein. Diese Invariante rechtfertigt leicht das Fixieren von Projektionen, unabhängig von den Feldtypen (dh sie können jede gewünschte Fixierinvariante auswählen). Natürlich ist diese Invariante nicht mit der Umwandlung von PinMut<T> in &mut T kompatibel. Wir sollten also besser sicherstellen, dass solche Typen nicht Drop oder Unpin .

Ich sehe keinen Zusammenhang mit der Leckpokalypse, stimme aber anders zu.

Nur eine Analogie - Unpin ist zu löschen, wie mem :: vergessen zu Rc-Zyklen ist. mem :: forget wurde ursprünglich als unsicher markiert, aber es gab keine Rechtfertigung dafür. (Und das gleiche Argument, dass Rc-Zyklen ein Randfall sind, wurde gegen das Markieren von mem :: forget safe angeführt.)

Beim Kopieren (spirituell) aus dem Discord würde ich wirklich gerne Beweise dafür sehen, dass wir das Problem nicht nur umgedreht haben: Unpin sicher zu implementieren, indem strukturelle Pin-Accessoren unsicher zu implementieren sind (dies würde auch bei jedem zutreffen) unsicheres Merkmal, das eingeführt wurde, richtig? Sie müssten immer noch unsicheren Code schreiben. Das ärgert mich, weil sie die meiste Zeit völlig sicher sind - im Grunde genommen, solange es kein explizites Unpin-Impl für den Typ gibt, genauso wie wir immer sicher sind, wenn es kein Drop-Impl für den Typ gibt. Mit dem aktuellen Plan benötigen wir etwas viel Stärkeres - es sollte ein explizites! Unpin-Impl für den Typ geben - was für viel weniger Typen gilt (das ist fast alles, was wir in einer Bibliothek tun können).

Leider habe ich keine Ahnung, wie oder ob der Compiler prüfen kann, ob es ein manuelles Unpin-Impl für einen bestimmten Typ gibt oder nicht, im Gegensatz zu "has any impl", und ich bin mir nicht sicher, ob es schlechte Interaktionen mit der Spezialisierung gibt. Wenn wir eine bestimmte Möglichkeit haben, diese Prüfung durchzuführen, so dass der Ersteller eines Typs keinen unsicheren Code schreiben muss, um strukturelle Fixierungen zu erhalten, wäre ich viel glücklicher, wenn impl Unpin sicher wäre, denke ich ... scheint das machbar zu sein?

Ich habe eine einfache Frage, die ich jetzt zu verstehen versuche. Wird Unpin in Generika eine implizite Bindung in der gleichen Größe sein, es sei denn, Ihre API für alle generischen Parameter?

das muss richtig sein, damit vecum weiterhin sicher zu sein.

wird eine implizite Bindung wie Größe lösen

Nein.

Das muss korrekt sein, damit vec weiterhin sicher ist.

Warum denkst du das?

Ärgerliche Sache, die @MajorBreakfast heute getroffen hat: PinMut::get_mut gibt Ihnen ein &mut mit der Lebensdauer des ursprünglichen PinMut , aber es gibt keinen sicheren Weg, dasselbe zu tun, da DerefMut gibt Ihnen einen Kredit mit der Lebensdauer des veränderlichen Verweises auf PinMut . Vielleicht sollten wir get_mut zum sicheren machen und get_mut_unchecked hinzufügen?

Du meinst so?

fn get_mut(this: PinMut<'a, T>) -> &'a mut T where T : Unpin

Ja, das sollten wir haben.

@ RalfJung Genau.

Die Methode aus der Futures-Kiste, in der ich dies verwenden möchte, sieht folgendermaßen aus:

fn next(&mut self) -> Option<&'a mut F> {
    self.0.next().map(|f| unsafe { PinMut::get_mut(f) })
}

Ich musste einen unsicheren Block verwenden, obwohl F an Unpin gebunden ist und absolut sicher ist. Derzeit gibt es keinen sicheren Weg, um eine Referenz zu erstellen, die die Lebensdauer von PinMut beibehält.

Ja, das ist nur eine Auslassung in der API.

Möchten Sie eine PR vorbereiten oder soll ich?

Ich kann es schaffen Wollen wir es get_mut und get_mut_unchecked ?

Ich werde ein sicheres map hinzufügen und das aktuelle auch in map_unchecked umbenennen. Hauptsächlich aus Gründen der Konsistenz. Dann enden alle unsicheren Funktionen von PinMut mit _unchecked und haben ein sicheres Äquivalent

Wollen wir es get_mut und get_mut_unchecked nennen?

Klingt vernünftig.

Ich werde einen sicheren map hinzufügen

Vorsicht dort. Wie soll es aussehen? Der Grund, warum die Karte unsicher ist, ist, dass wir nicht wissen, wie man eine sichere Karte erstellt.

Vorsicht dort. Wie soll es aussehen?

Du hast recht. Es muss ein Unpin an den Rückgabewert gebunden sein.

Ich hatte gerade eine Idee: Was ist mit PinArc ? Wir könnten so etwas in der BiLock impl (https://github.com/rust-lang-nursery/futures-rs/pull/1044) in der Futures-Kiste verwenden.

@MajorBreakfast PinArc (und PinRc usw.) hängt von Pin (der Nicht- Mut -Version) ab. Ich wäre bereit, es hinzuzufügen, aber ich war mir nicht sicher, ob es einen Konsens darüber gab, welche Garantien es bieten würde.

Ich bin mir nicht mehr so ​​sicher, ob das Hinzufügen von PinArc etwas hinzufügt ^^ ' Arc erlaubt bereits nicht, "aus geliehenen Inhalten herauszukommen".

@MajorBreakfast Mit Arc::try_unwrap können Sie aus Arc aussteigen. Es wäre eine rückwärts inkompatible Änderung, dies zu entfernen oder eine T: Unpin -Bindung einzuführen.

Hallo!
Ich habe ein wenig darüber nachgedacht, wie Pin-Accessoren sicher funktionieren können. Soweit ich weiß, tritt das Problem mit Pin-Accessoren nur dann auf, wenn Sie die Kombination T: !Unpin + Drop . Zu diesem Zweck habe ich einige Diskussionen gesehen, die versucht haben, die Möglichkeit eines solchen Typs T - z. B. die Merkmale !Unpin und Drop auf verschiedene Weise gegenseitig auszuschließen, aber dort war kein klarer Weg, dies zu tun, ohne die Abwärtskompatibilität zu beeinträchtigen. Mit Blick auf dieses Problem näher, können wir sicher Pin Accessoren erhalten , ohne einen Typ zu verhindern , dass sein !Unpin und Drop , es ist nur , dass eine solche Art nicht in einem genommen werden konnte Pin<T> . Zu verhindern, dass _das_ passiert, ist etwas, von dem ich glaube, dass es viel praktikabler ist, und mehr im Sinne dessen, wie das Merkmal Unpin sowieso funktioniert.

Derzeit habe ich eine "Lösung", um zu verhindern, dass ein Typ sicher ein Pin<T> , das sowohl !Unpin als auch Drop . Es erfordert Funktionen, die wir noch nicht in Rost haben, was ein Problem ist, aber ich hoffe, es ist ein Anfang. Wie auch immer, hier ist der Code ...

/// This is an empty trait, it is used solely in the bounds of `Pin`
unsafe trait Pinnable {}

/// Add the extra trait bounds here, and add it to the various impls of Pin as well
struct Pin<T: Pinnable> {
    ...
}

/// Then we impl Pinnable for all the types we want to be able to put into pins
unsafe impl<T: Unpin> Pinnable for T {}
unsafe impl<T: !Unpin + !Drop> Pinnable for T {}

Sowohl Unpin Typen als auch Typen, die !Unpin aber auch !Drop , haben (meines Wissens) keine Probleme mit Pin-Accessoren. Dies unterbricht keinen vorhandenen Code, der Drop , sondern begrenzt nur, was in eine Pin -Struktur eingefügt werden kann. In dem unwahrscheinlichen * Fall, in dem jemand einen Typ benötigt, der sowohl !Unpin als auch Drop (und tatsächlich in einem Pin ), kann er unsafe impl Pinnable für ihren Typ.

* Ich habe keine Erfahrung mit Pin , aber ich hoffe, dass die meisten Fälle, in denen jemand impl Drop für einen Typ benötigt, der !Unpin ist, nicht durch das verursacht werden Das, was gelöscht werden muss, ist von Natur aus !Unpin , sondern wird durch eine Struktur mit Drop Feldern und !Unpin Feldern verursacht. In diesem Fall könnten die Drop -Felder in eine eigene Struktur unterteilt werden, die die !Unpin -Felder nicht enthält. Ich kann bei Bedarf näher darauf eingehen, da ich der Meinung bin, dass diese Erklärung zweifelhaft war, aber dies war nicht als Hauptteil des Kommentars gedacht, daher werde ich es vorerst kurz lassen.

Soweit ich weiß, tritt das Problem mit Pin-Accessoren nur auf, wenn Sie die Kombination T :! Unpin + Drop haben.

Es ist eher ein "oder" als ein "und".

Pin-Accessoren implizieren eine "strukturelle" Interpretation des Fixierens: Wenn T fixiert ist, sind auch alle seine Felder fixiert. impl Unpin und Drop sind dagegen sicher, da sie davon ausgehen, dass es dem Typ überhaupt nicht darum geht, festgesteckt zu werden - T festgesteckt zu werden oder keinen Unterschied zu machen; Insbesondere sind die Felder von T in beiden Fällen nicht fixiert.

Sie haben dies jedoch später richtig verstanden, als Sie sagten, dass !Unpin + !Drop Typen diejenigen sind, für die Accessoren sicher hinzugefügt werden können. (Der Typ muss immer noch darauf achten, dass in seinem unsicheren Code nichts falsch gemacht wird, aber Unpin ad Drop sind die beiden sicheren Methoden, um Pinning-Accessoren zu brechen.)

Wenn wir uns dieses Problem genauer ansehen, können wir sichere Pin-Accessoren erhalten, ohne zu verhindern, dass ein Typ vorhanden ist! Unpin und Drop, es ist nur so, dass ein solcher Typ nicht in einen Pin gesteckt werden kann.

Ich folge nicht. Warum denkst du, ist das genug? Und wenn ja, warum sollten wir uns überhaupt für angeheftete Accessoren interessieren, wenn ein Typ nicht angeheftet werden kann ...?

Es ist eher ein "oder" als ein "und".

Lassen Sie uns erst einmal darüber sprechen, denn meine ganze Idee basierte auf dem Gegenteil davon. Wenn ich das falsch verstanden habe, hilft der Rest der Idee nicht weiter.

Nach meinem (aktuellen) Verständnis sind Pin-Accessoren mit einem Typ von Unpin völlig solide, unabhängig vom Ablegen, da Pin<T> &mut T sowieso effektiv !Drop auch für einen Typ mit einem !Unpin vollständig einwandfrei, da drop die einzige Funktion ist, die &mut self Beim Typ !Unpin wird nach Eingabe eines Pin nichts mehr verschoben.

Wenn ich dabei einen Fehler gemacht habe, lassen Sie es mich bitte wissen. Wenn nicht, dann wären Unpin + Drop Typen oder !Unpin + !Drop Typen mit Pin-Accessoren völlig in Ordnung, und die einzigen Problemtypen wären diejenigen, die !Unpin + Drop .

@ashfordneil

Außerdem sind Pin-Accessoren für einen Typ, der! Drop ist, selbst für einen! Unpin-Typ vollständig einwandfrei, da drop die einzige Funktion ist, die sich auf den! Unpin-Typ einstellen kann, sobald er in einen Pin eingibt, sodass nichts dazu in der Lage wäre Dinge herausbringen.

Wenn ich struct Foo(InnerNotUnpin); impl Unpin for Foo {} schreibe, ist es nicht sinnvoll, von PinMut<Foo> auf PinMut<InnerNotUnpin> . Damit die Projektion solide ist, müssen Sie (a) Drop-Impls verbieten und (b) sicherstellen, dass die Struktur nur dann Unpin , wenn das Feld Unpin .

(a) sollte nur das Verbot von Drop-Impls für Dinge verbieten, die !Unpin oder?
(b) sollte durch die Tatsache behandelt werden, dass die Implementierung von Unpin unsicher ist - sicher ist es möglich, sich in den Fuß zu schießen, indem man unsicher behauptet, dass ein Typ gelöst werden kann, wenn dies tatsächlich nicht möglich ist -, aber es ist genauso möglich, ihn zu kennzeichnen etwas wie Sync wenn es nicht sein sollte und auf diese Weise zu ungesundem Verhalten führt

(a) sollte nur verbieten, Drop-Impls auf Dinge zu setzen, die sind! Unpin richtig?

Ja, aber das ist aus Gründen der Abwärtskompatibilität unmöglich.

Und das kommt zu meinem Punkt - das Verbot von Drop-Implikationen für Dinge, die sind! Unpin ist aus Gründen der Abwärtskompatibilität unmöglich. Aber wir müssen nicht verhindern, dass Drop-Impls auf Dinge auftreten, die auftreten! Der! Unpin-Marker für einen Typ hat keine Bedeutung und macht keine Versprechungen, bis sich dieser Typ in einem Pin befindet. Daher ist der Drop-Impl auf einem! Unpin-Typ vollständig einwandfrei, solange sich dieser Typ nicht in einem Pin befindet. Was ich vorschlage, ist, dass wir Pin von Pin<T> in Pin<T: Pinnable> ändern, wobei das Merkmal Pinnable als Gatekeeper fungiert, um Typen zu verhindern, die Drop + !Unpin (oder mehr) sind Im Allgemeinen werden Typen, für die Pin-Accessoren problematisch sind, nicht in das Pin .

An diesem Punkt machen Sie das Merkmal Unpin völlig nutzlos, nicht wahr? Außerdem können Sie mit dem aktuellen Rust immer noch nicht die erforderlichen Grenzen ausdrücken. Wenn wir sagen könnten " Unpin und Drop sind nicht kompatibel", gäbe es kein Problem. Sie scheinen etwas Ähnliches mit Pinnable zu brauchen. Ich sehe nicht, wie das hilft.

Das Unpin-Merkmal allein ist bereits „nutzlos“. Das Merkmal _nur_ bedeutet etwas, sobald der Typ fixiert wurde, kann der Typ verschoben werden, bevor er in den Pin gelangt.

@RalfJung und ich hatten gerade ein Meeting, um über diese APIs zu sprechen, und wir waren uns einig, dass wir bereit sind, sie ohne größere API-Änderungen

Lösung offener Fragen

Mein Glaube an die Pin-APIs ist:

  1. Die Kern-API-Konzepte von PinMut und Unpin bilden die bestmögliche Lösung, die keine integrierte Sprachunterstützung beinhaltet.
  2. Diese APIs schließen eine zukünftige integrierte Sprachunterstützung nicht aus, wenn wir dies für erforderlich halten.

Es gibt jedoch einige offene Fragen zu ihren Einschränkungen und Kompromissen, für die ich unsere Entscheidung festhalten möchte.

Das Problem von Drop

Ein Problem, das wir erst nach dem Zusammenführen des RFC entdeckten, war das Problem des Merkmals Drop . Die Drop::drop -Methode verfügt über eine API, die sich selbst &mut self . Dies ist im Wesentlichen das "Aufheben" von Self und verletzt die Garantien, die angeheftete Typen bieten sollen.

Glücklicherweise ist diese keinen Einfluss auf die Solidität des Kern „unbewegliches Generator“ -Typen wir Projekt mit dem Stift versuchen: sie implementieren keinen destructor, so dass ihre !Unpin Umsetzung es eigentlich bedeutet.

Dies bedeutet jedoch, dass es für Typen, die Sie selbst definieren, zwangsläufig sicher ist, sie zu "lösen". Sie müssen lediglich Drop implementieren. Dies bedeutet, dass Unpin als unsafe trait uns keine zusätzliche Sicherheit verschaffte: Die Implementierung von Drop ist so gut wie die Implementierung von Unpin , was die Solidität betrifft besorgt.

Aus diesem Grund haben wir das Merkmal Unpin einem sicheren Merkmal für die Implementierung gemacht.

Pin-Projektion

Die Hauptbeschränkung bei der aktuellen API besteht darin, dass nicht automatisch festgestellt werden kann, dass die Durchführung einer Pin-Projektion sicher ist, wenn das Feld Unpin nicht implementiert. Eine Stiftprojektion ist:

Bei einem Typ T mit einem Feld a mit dem Typ U kann ich durch Zugriff von PinMut<'a, T> auf PinMut<'a, U> sicher "projizieren" das Feld a .

Oder im Code:

struct Foo {
    bar: Bar,
}

impl Foo {
    fn bar(self: PinMut<'_, Foo>) -> PinMut<'_, Bar> {
        unsafe { PinMut::map_unchecked(self, |foo| &mut foo.bar) }
    }
}

Sofern der Feldtyp nicht Unpin implementiert, muss rustc derzeit nicht beweisen, dass dies sicher ist. Das heißt, die Implementierung dieser Art von Projektionsmethode erfordert derzeit unsicheren Code. Glücklicherweise können Sie beweisen, dass es sicher ist. Wenn der Feldtyp Unpin möglicherweise nicht implementiert, ist dies nur dann sicher, wenn alle diese Bedingungen erfüllt sind:

  1. Das Selbst - Typ nicht implementiert Unpin , oder aber nur bedingt Arbeitsgeräte Unpin , wenn der Feldtyp der Fall ist.
  2. Der Self-Typ implementiert nicht Drop , oder der Destruktor für den Self-Typ verschiebt niemals etwas aus diesem Feld.

Bei Spracherweiterungen könnte der Compiler überprüfen, ob einige dieser Bedingungen erfüllt sind (z. B. dass Self Drop nicht implementiert und Unpin nur bedingt implementiert).

Aufgrund des Problems von Drop , einem stabilen Merkmal, bestand dieses Problem bereits bei jeder Entscheidung, die wir zu diesem Problem treffen konnten, und es gibt keine Möglichkeit ohne Sprachänderungen, die Pin-Projektion automatisch sicher zu machen. Eines Tages werden wir diese Änderungen möglicherweise vornehmen, aber wir werden die Stabilisierung dieser APIs nicht blockieren.

Angabe der "Pin" -Garantie

Spät im RFC-Prozess erkannte @cramertj , dass Pinning mit einer geringfügigen Erweiterung über das hinausgehen kann, was wir für unbewegliche Generatoren benötigen, um die Garantien für aufdringliche Listen sicher zu kapseln. Ralf schrieb eine Beschreibung dieser Erweiterung hier .

Die Garantie für das Fixieren lautet im Wesentlichen:

Wenn Sie ein PinMut<T> haben und T Unpin nicht implementiert, wird T nicht verschoben und der Speicher, der T , nicht ungültig gemacht werden T .

Dies ist nicht genau das Gleiche wie "Leckfreiheit" - T kann immer noch lecken, wenn er beispielsweise hinter einem Rc-Zyklus steckt. Selbst wenn er leckt, bedeutet dies, dass der Speicher niemals ungültig wird (bis das Programm wird beendet), da der Destruktor niemals ausgeführt wird.

Diese Garantie schließt bestimmte APIs für das Pinning von Stapeln aus, sodass wir uns zunächst nicht sicher waren, ob das Pinning um dieses erweitert werden soll. Letztendlich sollten wir uns jedoch aus mehreren Gründen für diese Garantie entscheiden:

  1. Andere, noch ergonomischere Stack-Pinning-APIs wie dieses Makro wurden entdeckt , um den Nachteil zu beseitigen.
  2. Der Kopf ist ziemlich groß! Diese Arten von aufdringlichen Listen könnten sehr wirkungs auf Bibliotheken wie josephine , die Rust mit Müll gesammelt Sprachen integrieren.
  3. Ralf war sich nie sicher, ob einige dieser APIs von Anfang an wirklich solide waren (insbesondere diejenigen, die auf "Dauerausleihe" basieren).

API-Reorganisation

Ich möchte jedoch eine letzte vorgeschlagene Änderung an der API vornehmen, nämlich das Verschieben von Dingen. Als Ralf & ich die vorhandenen APIs überprüften, stellten wir fest, dass es keinen guten Ort gab, um die Garantien und Invarianten auf hoher Ebene in Bezug auf Drop usw. zu beschreiben. Deshalb schlage ich vor

  1. Wir erstellen ein neues std::pin Modul. Die Moduldokumente bieten einen allgemeinen Überblick über das Fixieren, die von ihm bereitgestellten Garantien und die von den invarianten Benutzern der unsicheren Teile erwarteten Einhaltung.
  2. Wir verschieben PinMut und PinBox von std::mem und std::boxed in das Modul pin . Wir belassen Unpin im Modul std::marker mit den anderen Markierungsmerkmalen.

Wir müssen diese umfangreichere Dokumentation auch tatsächlich schreiben, bevor wir die Stabilisierung dieser Typen abgeschlossen haben.

Hinzufügen von Unpin Implementierungen

Ralf & ich haben auch darüber gesprochen, der Standardbibliothek weitere Implementierungen von Unpin hinzuzufügen. Bei der Implementierung von Unpin gibt es immer einen Kompromiss: Wenn Unpin Bezug auf den Feldtyp unbedingt implementiert wird, können Sie das Projekt nicht an dieses Feld anheften. Aus diesem Grund waren wir bisher nicht sehr aggressiv bei der Implementierung von Unpin .

Wir glauben jedoch, dass in der Regel:

  1. Die Stiftprojektion durch einen Zeiger sollte niemals als sicher behandelt werden (siehe Hinweisfeld unten).
  2. Die Stiftprojektion durch innere Veränderlichkeit sollte niemals als sicher behandelt werden.

Im Allgemeinen tun Typen, die eines dieser Dinge tun, etwas (z. B. die Neuzuweisung des Hintergrundspeichers wie in Vec ), wodurch die Pin-Projektion unhaltbar wird. Aus diesem Grund halten wir es für angebracht, allen Typen in std, die einen generischen Parameter entweder hinter einem Zeiger oder in einer UnsafeCell enthalten, eine bedingungslose Implementierung von Unpin hinzuzufügen. Dies schließt zum Beispiel alle std::collections .

Ich habe nicht zu genau nachgeforscht, aber ich glaube, dass es für die meisten dieser Typen ausreichen würde, wenn wir nur diese Impls hinzufügen:

impl<T: ?Sized> Unpin for *const T { }
impl<T: ?Sized> Unpin for *mut T { }
impl<T: ?Sized> Unpin for UnsafeCell<T> { }

Es gibt jedoch ein Problem: Technisch gesehen glauben wir, dass die Pin-Projektion durch Box technisch sicher gemacht werden kann: Das heißt, von PinMut<Box<T>> auf PinMut<T> kann sicher vorgegangen werden. Dies liegt daran, dass Box ein hundertprozentiger Zeiger ist, der sich niemals neu zuordnet.

Möglicherweise möchten wir ein Loch bereitstellen, sodass Unpin nur bedingt für Box<T> implementiert wird, wenn T: Unpin . Meine persönliche Meinung ist jedoch, dass es möglich sein sollte, sich das Fixieren als einen Speicherblock vorzustellen, der an Ort und Stelle fixiert wurde, und daher sollte jede Projektion durch Zeiger-Indirektion unsicher sein.

Wir könnten diese Entscheidung verzögern, ohne die Stabilisierung zu blockieren, und diese Implikationen im Laufe der Zeit hinzufügen.

Fazit

Ich würde wirklich gerne die Gedanken anderer Leute hören, die bisher sehr in die Pin-APIs investiert haben, und ob dieser Stabilisierungsplan vernünftig klingt oder nicht. Ich werde in einem nachfolgenden Beitrag eine kürzere Zusammenfassung mit einem fcp-Vorschlag für den Rest des Sprachteams machen.

@rfcbot fcp zusammenführen

Ich schlage vor, die vorhandenen Pin-APIs nach einer kleinen Reorganisation zu stabilisieren. Sie können eine längere Zusammenfassung in meinem vorherigen Beitrag lesen. Die TL; DR meiner Meinung nach ist, dass die aktuelle API ist:

  • Klang
  • So gut wie es ohne Sprachänderungen geht
  • Vorwärtskompatibel mit Sprachänderungen, die es besser machen würden

Wir haben alle wichtigen Fragen bezüglich der genauen Garantien und Invarianten der Fixierung in den letzten Monaten ausgearbeitet. Die Entscheidungen, die wir derzeit treffen (was meiner Meinung nach die Entscheidungen sind, bei denen wir uns stabilisieren sollten), sind in der längeren Veröffentlichung dokumentiert.

Ich schlage einige Änderungen vor, bevor wir die Dinge tatsächlich stabilisieren:

  1. Reorganisieren Sie PinMut und PinBox unter einem neuen std::pin -Modul, das eine allgemeine Dokumentation zu den Invarianten und Garantien für das Fixieren im Allgemeinen enthält.
  2. Fügen Sie bedingungslose Implementierungen von Unpin für die Typen in std hinzu, die generische Parameter entweder hinter einer Zeiger-Indirektion oder in einer UnsafeCell enthalten. Die Begründung, warum diese Implementierungen angemessen sind, ist in der längeren Zusammenfassung enthalten.

Teammitglied @withoutboats hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch die übrigen markierten Teams:

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

Derzeit sind keine Bedenken aufgeführt.

Sobald eine Mehrheit der Prüfer zustimmt (und kein Objekt mehr), wird dies in den endgültigen Kommentarzeitraum aufgenommen. 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.

@rfcbot fcp abbrechen

Ich tagge lang auch wegen der wichtigsten invarianten Entscheidungen, die wir treffen mussten, also storniere ich den FCP und starte ihn neu

@withoutboats Vorschlag storniert.

@rfcbot fcp merge Siehe vorherige Zusammenführungsnachricht und lange Zusammenfassung, sorry!

Teammitglied @withoutboats hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch die übrigen markierten Teams:

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [] @joshtriplett
  • [] @nikomatsakis
  • [x] @nrc
  • [] @pnkfelix
  • [x] @scottmcm
  • [] @sfackler
  • [x] @ohne Boote

Sorgen:

Sobald eine Mehrheit der Prüfer zustimmt (und kein Objekt mehr), wird dies in den endgültigen Kommentarzeitraum aufgenommen. 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.

Ich bin froh, dass ich zuversichtlich bin, dass dies richtig ist. Es ist jedoch schwierig, alles in den Griff zu bekommen, was sich seit dem ursprünglichen RFC geändert hat. Könnte der RFC-Text aktualisiert werden, um das zur Stabilisierung vorgeschlagene Design widerzuspiegeln? (Im Allgemeinen ist dies ein Problem, das viele RFCs bekommen, wenn sie sich während der Implementierungsphase so stark ändern, dass es sinnlos ist, durch Lesen des RFC einen Überblick darüber zu bekommen. Wir sollten dort einen Weg finden, es besser zu machen.)

RFCs sind keine Spezifikation. Das Lesen des RFC ersetzt nicht die eigentliche Dokumentation.

@bstrie Die signifikante Änderung gegenüber dem RFC ( Unpin ist sicher) wird in meinem zusammenfassenden Beitrag behandelt. Langfristig sollten die Benutzer ein Verständnis für die Pinning-API erlangen, indem sie die Dokumente in std::pin (einem Blocker für die Stabilisierung) lesen und nicht den ursprünglichen RFC ausgraben.

Wenn beabsichtigt ist, dass die Dokumentation die Stabilisierung blockiert, die Dokumentation jedoch noch nicht geschrieben wurde, ist dieser FCP dann nicht verfrüht? Die Erwartung, dass potenzielle Kommentatoren die vorläufige Dokumentation selbst rekonstruieren, indem sie unterschiedliche Quellen zusammensetzen, ist für IMO eine eher höhere Eintrittsbarriere, als man es gerne hätte.

@bstrie Es ist unser Standardverfahren, FCP vor der Dokumentation vorzuschlagen. Ich habe einen sehr ausführlichen zusammenfassenden Kommentar zum Stand der Dinge geschrieben, der genügend Informationen für alle enthalten sollte, die das Tracking-Problem nicht verfolgt haben, sowie einen kürzeren Kommentar für Leute, die nicht so viel Kontext wollen

FCP ist die Frage: "Wollen wir das stabilisieren?" Wenn diese Antwort "Nein" ergibt, wurde eine Menge Arbeit an Dokumenten verschwendet. FCP-Abschluss bedeutet nicht, dass die Funktion sofort stabil wird. es bedeutet, dass die Entscheidung getroffen wurde, es zu stabilisieren, und jetzt ist die Zeit gekommen, die dafür erforderliche Arbeit zu leisten; Dazu gehören Compiler- und Dokumentationsarbeiten.

Am 2. August 2018, um 12:56 Uhr, Boote [email protected] schrieb:

@bstrie Es ist unser Standardverfahren, FCP vor der Dokumentation vorzuschlagen. Ich habe einen sehr ausführlichen zusammenfassenden Kommentar zum Stand der Dinge geschrieben, der genügend Informationen für alle liefern sollte, die das Tracking-Problem nicht verfolgt haben

- -
Sie erhalten dies, weil Sie kommentiert haben.
Antworten Sie direkt auf diese E-Mail, zeigen Sie sie auf GitHub an oder schalten Sie den Thread stumm.

@ohne Boote

Die Stiftprojektion durch einen Zeiger sollte niemals als sicher behandelt werden (siehe Hinweisfeld unten).
Die Stiftprojektion durch innere Veränderlichkeit sollte niemals als sicher behandelt werden.

Können Sie diesen Punkt ein wenig klarstellen? Beziehen Sie sich speziell auf Typen in der Standardbibliothek ? Ähnlich wie bei Box gibt es Typen wie Mutex , die Pinning projizieren könnten, da sie ihren Inhalt eindeutig besitzen (obwohl sie möglicherweise außerhalb des Bandes liegen). Zumindest außerhalb der Standardbibliothek würde ich annehmen, dass wir die Möglichkeit haben möchten, Parallelitätsprimitive zu haben, die PinRef<InPlaceMux<T>> bis PinMut<T> bei einem Aufruf von .lock() projizieren, wie es scheint wie ein Fall von "Projektion durch innere Veränderlichkeit".

@cramertj Das Projizieren in ein Mutex scheint gefährlich; Man könnte die sichere Konvertierung in &Mutex und dann in Mutex::lock , um ein &mut und dann die Dinge zu verschieben.

EDIT: Oh, du meinst ein immer festgestecktes Mutex . Wie PinMutex . Ja, das würde funktionieren. Die innere Veränderlichkeit ist in Ordnung, wenn Sie niemals &mut . Derzeit ist es jedoch unwahrscheinlich, dass wir solche Datenstrukturen in libstd haben werden.

Aber ja, für volle Präzision sollte die Aussage von Stiftprojektion durch innere Veränderlichkeit nur dann sicher ist, wenn der Typ immer pins, dh niemals &mut austeilt ".

@ RalfJung Genau, ja.

Derzeit ist es jedoch unwahrscheinlich, dass wir solche Datenstrukturen in libstd haben werden.

Richtig, ich wollte sicherstellen, dass die angegebenen Anforderungen bezüglich des Nichtprojektierens durch Zeiger oder der inneren Veränderlichkeit speziell libstd und nicht allgemeine Regeln betreffen.

@cramertj Es ist definitiv keine feste Regel (es ist nicht UB, um es zum Beispiel zu verletzen), aber ich würde sagen, es ist eine gute Richtlinie für Leute, die unsicher sind

Ich bin enttäuscht von der Entscheidung, dies zu stabilisieren. Ich bin nicht übermäßig besorgt darüber, was kurzfristig passiert, da ohne Compiler-Unterstützung jedes potenzielle Design ein bisschen hackisch aussehen wird. Auf lange Sicht fühlen sich Rust jedoch nicht wirklich erstklassig, wenn sie einheimische unbewegliche Typen erhalten, es sei denn, sie können mit Merkmalsmethoden verwendet werden, die &self und &mut self akzeptieren. Infolgedessen muss die Fixierung eine Eigenschaft des Referenztyps sein, nicht des Referenztyps. Das kann natürlich auch in einem zukünftigen Design für native unbewegliche Typen passieren. Aber es würde dazu führen, dass Pin<T> überflüssig wird - nicht nur ein veralteter Alias ​​für einige native &pin T , sondern insgesamt unnötig. Im Gegenzug müssen Merkmale wie Future (und möglicherweise viele andere), die Pin<Self> akzeptieren, überarbeitet oder veraltet werden, was eine unordentliche Übergangszeit erfordern würde.

Chaotisch, nicht unmöglich. Da ich jedoch die Gefahr habe, übermäßig pessimistisch zu sein, mache ich mir Sorgen, dass der Wunsch, einen solchen Übergang zu vermeiden, zukünftige Entscheidungen dahingehend beeinflussen wird, stattdessen &pin als Grundlage für einheimische unbewegliche Typen zu verwenden - was meiner Ansicht nach weggehen wird sie dauerhaft zweitklassig. Und ich denke immer noch, dass bei kurzfristigen Designs der &Pinned<T> -Ansatz machbar gewesen wäre, um diese Bedenken hinsichtlich der Vorwärtskompatibilität zu vermeiden und auch andere Vorteile zu haben (z. B. das Problem der inneren Veränderlichkeit vollständig zu umgehen).

Naja. So wie es ist, freue ich mich immer noch darauf, Pin , aber ich hätte es vorgezogen, wenn es auf crates.io geblieben wäre, anstatt die Standardbibliothek zu betreten.

Ich stimme zu, dass es zu früh ist, dies zu stabilisieren. Derzeit ist es in einigen Situationen etwas umständlich, beispielsweise beim Zugriff auf Felder vom Typ !Unpin innerhalb einer Methode, die für PinMut<Self> aufgerufen wird. Ich denke, dass Änderungen ohne

@Thomasdezeeuw Welche Änderungen würden Ihrer Meinung nach die Situation verbessern

Es gibt keine Möglichkeit, eine generische Lösung zu schreiben, dh eine Methode zu std hinzuzufügen, aber diese Variation des Makros von Futures ist sicher:

macro_rules! get_mut {
    ($f:tt: $t:ty) => (
        fn $f<'a>(
            self: &'a mut std::mem::PinMut<Self>
        ) -> &'a mut $t
            where $t: Unpin
        {
            unsafe {
                 &mut std::mem::PinMut::get_mut_unchecked(self.reborrow()).$f
            }
        }
    )
}

struct Foo {
    bar: Bar,
}

impl Foo {
     get_mut!(bar: Bar);
}

@withoutboats Das ist sicher, weil es nur Zugriff auf Unpin Daten bietet, oder?

@ RalfJung genau! Die where-Klausel macht es sicher

@withoutboats das ist ein schönes Makro, funktioniert aber nur für Unpin Typen.

Aber wäre es nicht möglich, eine Methode / ein Makro hinzuzufügen, die PinMut<Self> annehmen und PinMut<Self.field> , vorzugsweise in sicherem Code. Ich könnte mich irren, aber ich denke, wenn der Anrufer garantiert, dass Self fixiert ist, dann ist Self.field auch richtig? Dies sollte auch für Typen funktionieren, die Unpin nicht implementieren.

@Thomasdezeeuw Dieses Problem wird in meiner Zusammenfassung im Abschnitt "Pin-Projektion" behandelt. Es ist nicht sicher, in !Unpin -Felder zu projizieren, es sei denn, Sie garantieren auch, dass Self bestimmte andere Dinge nicht tut, für die wir keine Prüfung generieren können. Wir können nur automatisch die Fähigkeit garantieren, auf Felder zu projizieren, die Unpin implementieren (da dies immer sicher ist und wir dies mit einer where -Klausel überprüfen können).

@withoutboats Ich habe die Zusammenfassung gelesen, aber vielleicht habe ich falsch verstanden. Wenn jedoch ein Typ T !Unpin ist, implementiert DerefMut for T PinMut<T> DerefMut for T , sodass das Verschieben des Werts ohne unsicheren Code nicht möglich wäre. Sie haben immer noch das Problem mit dem Merkmal Drop , aber das ist größer, als nur Zugriff auf das Feld eines Typs zu erhalten. Ich verstehe also nicht, was die Zuordnung von PinMut<T> zu PinMut<T.field> unsicher macht, selbst wenn T.field !Unpin .

@Thomasdezeeuw In Futures-Rs haben wir Situationen, in denen wir einen Typ innerhalb eines PinMut , aber eines seiner Felder nicht als fixiert betrachtet wird

Der Nachteil des Makros von @withoutboats ist, dass es jeweils nur auf ein Feld zugreifen kann, da es einen veränderlichen Verweis auf self . Strukturfeldzugriffe haben diese Einschränkung nicht.

Ich glaube, die Entscheidung, die Pinning-API zu stabilisieren, ist die richtige Entscheidung. Ich würde jedoch vorschlagen, zuerst das vorgeschlagene core::pin -Modul zu implementieren.

Sie haben immer noch das Problem mit dem Drop-Merkmal, aber das ist größer, als nur Zugriff auf das Feld eines Typs zu erhalten.

Nein, ist es nicht. Drop ist eigentlich in Ordnung , solange Sie keine Feldzuordnung durchführen . Aus diesem Grund ist die Feldzuordnung unsicher.

Sie haben immer noch das Problem mit dem Drop-Merkmal, aber das ist größer, als nur Zugriff auf das Feld eines Typs zu erhalten. Ich verstehe also nicht, was das Mapping von PinMut ausmachtzu PinMutunsicher, auch wenn T.field ist! Unpin.

Um klar zu sein, können wir auch nicht automatisch einen Beweis dafür generieren, dass T: !Unpin . Aber auch wenn dies nicht der Fall ist, finden Sie hier ein Beispiel dafür, wie das Drop impl verwendet werden kann:

struct Foo {
    field: UnpinFuture,
}

impl Foo {
     fn field(self: PinMut<Self>) -> PinMut<UnpinFuture> { ... }

     fn poll_field(self: PinMut<Self>, ctx: &mut Context) {
         self.field().poll(ctx);
     }
}

impl Drop {
    fn drop(&mut self) {
        // ...
        let moved_field = mem::replace(&mut self.field, UnpinFuture::new());

        // polling after move! violated the guarantee!
        PinBox::new(moved_field).as_pin().poll();
    }
}

Wie @RalfJung sagt, ist dies genau das Problem mit Drop - wenn Sie keine Pin-Projektionen auf !Unpin -Felder durchführen, ist alles in Ordnung, was Sie in Drop tun.

Der Nachteil des Makros von @withoutboats ist, dass es jeweils nur auf ein Feld zugreifen kann, da es einen veränderlichen Verweis auf self enthält. Strukturfeldzugriffe haben diese Einschränkung nicht.

Sie können die Methode schreiben, die den Zugriff auf mehrere Felder ermöglicht, müssen dies jedoch für jede Kombination tun, die Sie interessiert.

Gibt es aus Neugier neben der Zukunft noch andere konkrete Anwendungsfälle oder zumindest etwas anderes, das den Wunsch motiviert, diese jetzt zu stabilisieren (im Gegensatz zu später mit mehr Erfahrung)?

Es scheint hier keine weitere Diskussion über die von @comex angesprochenen Punkte zu geben. Sie haben mich ziemlich neugierig gemacht, weil ich mich nicht an die Idee erinnere, pin eine Eigenschaft des Typs anstelle der Referenz zu machen. Wurde dies bereits an anderer Stelle besprochen? Es ist nicht einfach, alles von "außen" zu verfolgen, ich gebe mein Bestes ;-)

Im Gegenzug Merkmale wie Future (und möglicherweise viele andere), die Pin akzeptierenmüssen überarbeitet oder veraltet werden, was eine unordentliche Übergangszeit erfordern würde.

Hmmm. Könnte uns eine editionsbezogene Magie erlauben, diese Pin s still zu verpacken und zu entpacken und die Änderungen vollständig abwärtskompatibel zu machen? Ein bisschen eklig, aber nicht übermäßig, und es würde die Sprache und die APIs während eines solchen Übergangs sauber halten.

Vielleicht müsste die Idee weiter ausgearbeitet werden, um diese Frage zu beantworten (ich erinnere mich, dass dies zuvor im Thread angesprochen wurde, aber ich habe den Überblick verloren, wo.)

@yasammez konnte ich die Diskussion der wertorientierten abholen Pinning um hier

Nur um zu bestätigen, dass der Weg zur Stabilisierung keinen Plan für sichere Feldprojektionen enthält? Es scheint mir, dass dies eine Menge unsicheren Codes in alle Zukunftsbibliotheken einführen wird. Nach meiner Erfahrung sind manuelle Future-Impls keine Seltenheit, und ich weiß, dass alle meine Felder abfragen müssen.

Ich denke nicht, dass es wichtig ist, dass ein Großteil dieses unsicheren Codes trivial als sicher erwiesen wird. Die bloße Existenz des unsicheren Codes beeinträchtigt die Fähigkeit, realeren unsicheren Code zu prüfen.

@tikue in naher

  1. Verwenden Sie ein Makro wie das Makro unsafe_pinned , das die Futures-Bibliothek entwickelt hat, und beschränken Sie sich für jede innere Umfrage auf eine einzige unsichere, die Sie anhand der in meiner langen Zusammenfassung beschriebenen Einschränkungen überprüfen müssen (Sie können auch kein Makro und verwenden Schreiben Sie den Accessor einfach von Hand, es ist auch nur ein einziges unsafe ).
  2. Fordern Sie Ihre Felder auf, Unpin zu implementieren, um solche Projektionen trivial sicher zu machen und keinen unsicheren Code zu erfordern, auf Kosten der Heap-Zuweisung von !Unpin Futures.

@withoutboats Ich verstehe die Optionen, finde sie aber schwer zu Unpin schränkt die Kompatibilität mit einer ganzen Reihe von Futures ein, wie alles, was sich selbst ausleiht, z. B. jede asynchrone Verwendung von Kanälen. Und unsichere Umweltverschmutzung ist ein echtes Problem.

In der Praxis habe ich versucht, eine Bibliothek auf Futures 0.3 zu migrieren, und aus diesen Gründen festgestellt, dass dies ein Kampf ist.

@tikue Dies ist vorwärtskompatibel mit der automatisierten Bereitstellung des Schecks für Sie. Wir haben das noch nicht vollständig entworfen oder implementiert, aber nichts hier hindert es daran.

Ich verstehe dieses Problem jedoch nicht wirklich:

Und unsichere Umweltverschmutzung ist ein echtes Problem.

Das "Problem der Unsicherheit" bleibt meiner Meinung nach zu oft auf diesem Niveau - nur ein generelles Verbot von unsafe .

Bei den meisten verschachtelten Futures sollte es sehr einfach sein, festzustellen, ob Sie das Makro unsafe_pinned! sicher verwenden können. Die Checkliste sieht normalerweise so aus:

  1. Ich habe Unpin für meine Zukunft nicht manuell implementiert, sondern verlasse mich nur auf das Auto Trait Impl.
  2. Ich habe Drop für meine Zukunft nicht manuell implementiert, da ich keinen benutzerdefinierten Destruktor habe.
  3. Das Feld, in das ich projizieren möchte, ist privat.

In diesem Fall: Du bist gut! unsafe_pinned ist sicher zu verwenden.

Wenn Sie haben manuell implementieren Unpin oder Drop , Sie haben tatsächlich darüber nachdenken, aber selbst in diesem Fall ist es nicht unbedingt so ein schwieriges Problem:

  1. Wenn ich Unpin implementiert habe, ist dies auf die Zukunft beschränkt, die ich abstrahiere, weil ich Unpin bin.
  2. Wenn ich Drop implementiert habe, verschiebe ich das zukünftige Feld während meines Destruktors nie.

@tikue als Referenz habe ich einen Kern des Grundvorschlags für eine attributbasierte Lösung geschrieben . Dies erfordert Compiler-Unterstützung, jedoch keine wesentlichen Erweiterungen der Sprache - nur ein neues Merkmal und ein neues integriertes Attribut.

Es gibt auch eine "aufdringlichere" Lösung, bei der wir geeignete &pin Referenztypen hinzufügen, die in Bezug auf die Einschränkungen dieselbe Form haben würden, aber die Sprache insgesamt stärker beeinflussen würden.

Was ist mit PinMut::replace ?

impl<'a, T> PinMut<'a, T> {
  pub fn replace(&mut self, x: T) { unsafe {
    ptr::drop_in_place(self.inner as *mut T);
    ptr::write(self.inner as *mut T, x);
  } }
}

Selbst wenn wir eine solche Methode nicht hinzufügen möchten, sollten wir eine Antwort auf die Frage haben, ob dies sicher ist - für PinMut und auch für PinBox die etwas Ähnliches haben könnten.

Ich denke, es ist sicher sicher für PinBox , weil ich darauf achte, den Destruktor "an Ort und Stelle" zu nennen, und dann wird nur der Speicher wiederverwendet. Ich mache mir jedoch ein bisschen Sorgen um PinMut darüber, dass der Wert vorzeitig gesenkt wird, dh bevor seine Lebensdauer abgelaufen ist. Es scheint mir nicht klar zu sein, dass dies immer in Ordnung sein wird, aber ich kann auch kein Gegenbeispiel finden. @cramertj @withoutboats irgendwelche Gedanken?

(Ich würde dies zu @rfcbot hinzufügen, wenn ich könnte ...)

@rfcbot Anliegen ersetzen

@RalfJung Ich könnte mich hier irren, aber könnte dieser Code nicht doppelt fallen, wenn T Panik gerät?

@RalfJung Wir haben bereits PinMut::set .

@cramertj D'oh. Ja, okay, ich hätte das etwas gründlicher überprüfen können.

Entschuldigung für den Lärm, meine Sorge ist gelöst.

@rfcbot Auflösung ersetzen

Dies ist echter Code, den ich gerade für Futures 0.1 => 0.3-Kompatibilität mit tokio_timer::Deadline schreibe. Es muss ein PinMut<Future01CompatExt<Delay>> , wobei Delay ein Feld von Deadline .

        let mut compat;
        let mut delay = unsafe {
            let me = PinMut::get_mut_unchecked(self);
            compat = Future01CompatExt::compat(&mut me.delay);
            PinMut::new_unchecked(&mut compat)
        };

Neben der nicht trivialen Verwendung von unsicher hat ich auch ungefähr 5 Iterationen für diesen Code benötigt, um eine kompilierte Version zu finden. Das Schreiben und Nachdenken darüber, was hier vor sich geht, ist ziemlich unergonomisch.

Ich denke, zwischen einem generellen Verbot von unsicherem und diesem Code ist viel Platz. Zum Beispiel ist map_unchecked zu restriktiv, um mit diesem Code zu helfen, da eine Referenz zurückgegeben werden muss, während für diesen Code die Rückgabe von Future01CompatExt<&mut Delay> erforderlich ist.

Bearbeiten: Eine weitere Quelle der Unergonomie ist, dass Sie sich nicht in Match Guards ausleihen können, also PinMutkann mit Code wie diesem nicht arbeiten:

`` `Rost
mit self.poll_listener (cx) übereinstimmen? {
// war früher wenn self.open_connections> 0
Poll :: Ready (Keine) wenn self.open_connections ()> 0 => {
^^^^ veränderlich im Pattern Guard ausgeliehen
return Poll :: Pending;
}}
Umfrage :: Bereit (Keine) => {
return Poll :: Ready (Keine);
}}
}}
`` ``

Ihr Kommentar zu map_unchecked ist zutreffend. Möglicherweise gibt es eine allgemeinere Signatur, mit der auch temporäre Elemente um Zeiger zurückgegeben werden können.

Gibt es Richtlinien, wann es sicher ist, eine &mut Referenz aus einem Feld von PinMut<Type> ? Ich denke, solange Type nie davon ausgeht, dass das Feld fixiert ist, ist es in Ordnung? Muss es privat sein?

Ja, entweder implementiert das Feld Unpin oder Sie erstellen niemals ein PinMut des Feldes.

@ rfcbot Varianz

Ich schreibe eine anständige Menge von PinMut -basiertem Code und eine Sache, die viel passiert ist, ist, dass ich eine Operation habe, die nicht auf Pinning beruht, sondern sich selbst um PInMut anstatt &mut zu sagen "Ich verspreche, ich werde nicht aus dieser Erinnerung herauskommen." Dies zwingt jedoch alle Benutzer dazu, einen angehefteten Wert zu haben, was nicht erforderlich ist. Ich habe darüber nachgedacht und konnte keine gute API dafür finden, aber es wäre wunderbar, wenn es eine Möglichkeit gäbe, "Ich muss mein Argument festnageln" von "Ich kann mit einem angehefteten arbeiten" zu trennen Streit".

Ich denke, "Varianz" ist dort der falsche Begriff; Sie möchten, dass zugeordnete Typkonstruktoren über verschiedene Referenztypen abstrahieren: D.

@RalfJung Ja, das stimmt. Ich würde mich jedoch mit einem Typ zufrieden geben, der von einem PinMut oder einem & mut stammen könnte, aber nur wie ein PinMut verwendet werden kann, obwohl ich weiß, dass dies nicht besonders gut skaliert. Ich denke, das ist besser erreichbar als generische Arbitrary_self_types: smile: Vielleicht ist es am besten, einfach mit "Wir sind ziemlich sicher, dass wir zukunftssicher sind, um Signaturen wie diese zu funktionieren" zu schließen:

impl MyType {
    fn foo(self: impl MutableRef<Self>, ...) { ... }
}

@rfcbot betrifft API-Refactor

Ein bisschen Inspirationsstruktur und letzte Nacht habe ich herausgefunden, wie wir diese API so umgestalten können, dass es nur einen Pin -Typ gibt, der einen Zeiger umschließt, anstatt für jeden einzelnen Zeiger eine angeheftete Version erstellen zu müssen. Dies ist in keiner Weise eine grundlegende Umgestaltung der API, aber es fühlt sich besser an, die Komponente "Pins the Memory" zu einem zusammensetzbaren Teil herauszuziehen.

Als ich am Netzwerkstapel für nt-rs arbeitete , wurde mir gesagt, dass angeheftete Referenzen beim "Besitzertanz" helfen würden, den ich derzeit mit fold löse, wie hier gezeigt (tx.send verschiebt tx in eine Send<<type of tx>> Zukunft, die es schwierig macht, eingehende Daten zu durchlaufen, um Pakete zu senden.). Inwiefern würde das Feststecken bei so etwas helfen?

@Redrield send() nimmt &mut self in Futures 0.3.

@withoutboats Ich bin gespannt darauf, diese neue API in Futures-Rs auszuprobieren!

Da die neue API unterschiedliche Bezeichner verwendet, sollte es meiner Meinung nach möglich sein, beide APIs gleichzeitig verfügbar zu haben. Ich denke, es wäre am besten, eine kurze Übergangszeit zu haben, in der beide APIs verfügbar sind, damit wir experimentieren, eine PR vorbereiten und die Futures-API in libcore auf den neuen Stil portieren können.

@MajorBreakfast Es scheint, als könnten wir möglicherweise den Alias type PinMut<T> = Pin<&mut T>; und eine Reihe derselben Methoden anbieten, die die schiere Größe und Unmittelbarkeit des Bruchs verringern würden.

@ohne Boote

Also mit der neuen API:

  1. Pin<&T> und Pin<&mut T> haben die Invariante "Standard Pinning", wobei der Wert dahinter steht:
    1.a. ist kein gültiges &T / &mut T mehr, es sei denn, Unpin
    1.b. wird nicht als Pin von einer anderen Speicheradresse verwendet.
    1.c. wird gelöscht, bevor der Speicher ungültig wird.
  2. Pin<Smaht<T>> hat keine "speziellen" Garantien, außer dass es auf Anfrage gültige Pin<&mut T> und Pin<&T> zurückgibt (dies ist nur die Standard-Sicherheitsgarantie, da die APIs sicher sind ).
    2.a. Wollen wir eine DerefPure Garantie, bei der Pin<Smaht<T>> erforderlich ist, um denselben Wert zurückzugeben, sofern er nicht mutiert ist? Will jemand das?

Korrektur über die Invariante.

Die Invariante für Pin<Smaht<T>> ist:

  1. Wenn Sie Deref::deref(&self.inner) aufrufen, erhalten Sie ein gültiges Pin<&T::Target> (beachten Sie, dass &self.inner kein sicheres &Smaht<T> ).
  2. Wenn Smaht<T>: DerefMut , ruft DerefMut::deref_mut(&mut self.inner) wird eine gültige geben Pin<&mut T::Target> (beachten Sie, dass &mut self.inner braucht kein sicherer zu sein &mut Smaht<T> ).
  3. Das Aufrufen des Destruktors von self.inner , um ihn zu zerstören, ist in Ordnung, solange er für die Zerstörung sicher ist.
  4. self.inner muss kein sicherer Smaht<T> - es muss keine anderen Funktionen unterstützen.

Im reddit-Beitrag gab es einige Rückmeldungen zum Vorschlag von

  • (mehrfach) Own ist ein seltsamer Name. Weitere Möglichkeiten zur Lösung dieses Problems finden Sie weiter unten.

  • ryani fragt, warum die Implementierungen, die Deref<Target = T> einschränken, dieses zusätzliche generische T ? Kannst du nicht einfach P::Target ?

  • jnicklas fragt, was der Zweck des Merkmals Own ist. Was ist mit dem Hinzufügen von pinned inhärenten Methoden gemäß Konvention? Dies bedeutet, dass die API-Benutzer das Merkmal nicht importieren müssen, Sie jedoch die Fähigkeit verlieren, generisch gegenüber feststeckbaren Konstruktoren zu sein. Ist das eine große Sache? Die Motivation für das Merkmal scheint nicht ausreichend motiviert zu sein: [sie] haben immerhin die gleiche Form. '

  • RustMeUp (ich selbst) fragt im Merkmal Own , wozu die Methode own . Warum kann das Merkmal nicht einfach pinned von den Typen implementieren lassen, die sich anmelden möchten? Dies ermöglicht, dass das Merkmal sicher ist und dass das Merkmal Pinned was sich weniger unangenehm anfühlt als Own . Der Grund für die eigene Methode scheint nicht ausreichend motiviert zu sein.

Ich werde noch eine Frage hinzufügen:

Warum sind weder Pin<P> selbst noch Pin<P>::new_unchecked auf P: Deref ?

#[derive(Copy, Clone)]
pub struct Pin<P> {
    pointer: P,
}

impl<P: Deref> Pin<P> { // only change
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

oder

#[derive(Copy, Clone)]
pub struct Pin<P: Deref> { // changed
    pointer: P,
}

impl<P: Deref> Pin<P> { // changed
    pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
        Pin { pointer }
    }
}

beide würden Pin<P> wo P: !Deref Instanzen, die nutzlos sind, da es nichts gibt, was Sie damit nicht können, es sei denn, zusätzliche Methoden werden hinzugefügt. Meiner Ansicht nach...

Hier ist eine Zusammenfassung meiner und des Feedbacks von ryani .

Ryani fragt, warum die Implementierungen Deref einschränkenHaben Sie dieses zusätzliche generische T? Können Sie nicht einfach P :: Target verwenden?

Es gibt überhaupt keinen Unterschied zwischen diesen beiden Impls, außer wie sie geschrieben sind.

Warum sind weder Pin

selbst noch Pin

:: new_unchecked beschränkt auf P: Deref?

Ich habe keine Meinung; Historisch gesehen ist die Standardbibliothek nicht an Strukturen gebunden, aber ich bin mir nicht sicher, ob diese Richtlinie gut motiviert ist oder ein historischer Unfall ist.


Wenn Sie planen, dies zu implementieren, nachdem # 53227 gelandet ist, wird das Merkmal Own nicht implementiert, da es umstritten ist. Im Moment nur ein pinned inhärenter Konstruktor für Box , Rc und Arc .

Ich verfolge etwas, was Pin hier als tatsächlich auf " Zeigertypkonstruktoren " wirkend - Dinge wie Box oder &'a mut , die "Art" * -> * . Natürlich haben wir dafür eigentlich keine Syntax, aber auf diese Weise kann ich dies in Bezug auf Invarianten verstehen. Wir haben noch 4 (Sicherheit (Invarianten pro Typ wie in meinem Blog-Beitrag : Besessen, Geteilt, Gepinnt-Besitz, Gepinnt-Geteilt).

Ich denke, eine ordnungsgemäße Formalisierung erfordert diese Ansicht, da die Idee ist, dass Pin<Ptr><T> die Invarianten von T , sie transformiert (wobei die angehefteten Invarianten überall verwendet werden) und dann die Ptr anwendet Owned , aber das habe ich sowieso langfristig geplant.) In Bezug auf den Formalismus würde man es wirklich vorziehen, Ptr<Pin<T>> zu schreiben, aber wir haben es versucht das und es funktioniert nicht sehr gut mit Rust.

Das Versprechen, das ein Konstruktor vom Zeigertyp macht, wenn er sich für Pin "anmeldet", ist, dass das Anwenden von Deref / DerefMut sicher ist: Wenn die Eingabe tatsächlich Ptr Wird auf die Pinned-Owned / Shared-Variante des Typs angewendet. Die Ausgabe erfüllt die Pinnd-Owned / Shared-Variante dieses Typs.

Dies sind wirklich die einzigen zwei "unsicheren Methoden" hier (in dem Sinne, dass sie mit Invarianten vorsichtig sein müssen):

impl<P, T> Pin<P> where
    P: Deref<Target = T>,
{
    pub fn as_ref(this: &Pin<P>) -> Pin<&T> {
        Pin { pointer: &*this.pointer }
    }
}

impl<P, T> Pin<P> where
    P: DerefMut<Target = T>,
{
    pub fn as_mut(this: &mut Pin<P>) -> Pin<&mut T> {
        Pin { pointer: &mut *this.pointer }
}

Alles andere, was sicher zu verwenden ist, kann darüber hinaus mit sicherem Code implementiert werden, wobei ausgenutzt wird, dass Pin<&T> -> &T eine sichere Konvertierung ist, und für Unpin Typen gilt das gleiche für Pin<&mut T> -> &mut T .

Ich würde es vorziehen, wenn wir die Implementierungen Deref und DerefMut für Pin in so etwas ändern könnten

impl<'a, T: Unpin> Pin<&'a mut T> {
    pub fn unpin_mut(this: Pin<&'a mut T>) -> &'a mut T {
        this.pointer
    }
}

impl<'a, T> Pin<&'a T> {
    // You cannot move out of a shared reference, so "unpinning" this
    // is always safe.
    pub fn unpin_shr(this: Pin<&'a T>) -> &'a T {
        this.pointer
    }
}

// The rest is now safe code that could be written by users as well
impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        Pin::unpin_shr(Pin::as_ref(self))
    }
}

impl<P, T> DerefMut for Pin<P> where
    P: DerefMut<Target = T>,
    T: Unpin,
{
    fn deref_mut(&mut self) -> &mut T {
        Pin::unpin_mut(Pin::as_mut(self))
    }
}

Das würde den Punkt nach Hause bringen, dass diese beiden nichts Neues machen , sondern nur as_ref / as_mut mit einer sicheren Konvertierung von Pin<&[mut] T> in &[mut] T - zusammensetzen. - wodurch as_ref / as_mut eindeutig das einzige ist, worüber sich andere Zeigertypkonstruktoren Sorgen machen müssen.


Die aktuellen PinMut haben eine borrow -Methode, &mut self der self kostet, erhalten wir eine automatische Ausleihe). Diese Methode entspricht genau Pin::as_mut . Ich nehme an, wir würden auch hier so etwas wollen?

Ich habe gerade bemerkt, dass Slices get_unchecked_mut , aber Pin hat get_mut_unchecked . Scheint, als sollten wir uns an etwas Konsequentes halten?

Könnte jemand dies zu den Bedenken von rfcbot hinzufügen?

@rfcbot betrifft get_mut_unchecked_mut_mut

Ich habe gerade festgestellt, dass wir eine Situation vergessen haben, in der rustc Sachen kopiert - es ist wahrscheinlich vorerst keine große Sache. Ich spreche von gepackten Strukturen. Wenn eine gepackte Struktur ein Feld enthält, das gelöscht werden muss, gibt rustc Code aus, um die Daten dieses Felds an eine ausgerichtete Stelle zu kopieren, und ruft dann drop auf. Damit soll sichergestellt werden, dass das an &mut übergebene drop tatsächlich ausgerichtet ist.

Für uns bedeutet dies, dass eine repr(packed) -Struktur nicht "strukturell" oder "rekursiv" sein darf. pinning - seine Felder können nicht als fixiert betrachtet werden, selbst wenn es sich um die Struktur selbst handelt. Insbesondere ist es unsicher, das Pin-Accessor-Makro für eine solche Struktur zu verwenden. Das sollte zu seiner Dokumentation hinzugefügt werden.

Das scheint eine riesige Fußwaffe zu sein, die über alle Pinning-Dokumente geklebt werden sollte.

@alercah Ich denke nicht, dass es viel mehr eine Fußwaffe ist als die vorhandene Fähigkeit, Unpin oder Drop zu implizieren - sicherlich werden beide neben angehefteten Werten weitaus häufiger gesehen als #[repr(packed)] .

Das ist fair. Ich mache mir Sorgen, dass jemand denkt, dass eine Projektion für einen Typ sicher ist, den er nicht geschrieben hat, und nicht erkennt, dass packed dies unsicher macht, weil dies entschieden nicht offensichtlich ist. Es ist wahr, dass jeder, der die Projektion macht, dafür verantwortlich ist, sich dessen bewusst zu sein, aber ich denke, es muss zusätzlich überall dort dokumentiert werden, wo eine solche Projektion auftreten könnte.

Hm, würde dies auch bedeuten, dass alle gepackten Strukturen unabhängig von den Feldtypen Unpin sein könnten, da das Fixieren nicht rekursiv ist?

@alercah Ich bin nicht sehr vertraut damit, wie gepackte Typen implementiert werden, aber ich glaube, es ist sicher, an einen gepackten Typ zu heften , nur nicht durch einen gepackten Typ zu pinnen. Der einzige Fall, in dem Sie den gepackten Typ nicht kontrollieren, ist, wenn Sie in ein öffentliches Feld des Typs eines anderen projizieren, das aufgrund von Drop oder etwas anderem genauso unsicher sein kann. Im Allgemeinen scheint es nicht ratsam, das Projekt an die Felder eines anderen zu heften, es sei denn, dieser liefert eine Stiftprojektion, die anzeigt, dass es sicher ist.

Hm, würde dies auch bedeuten, dass alle gepackten Strukturen unabhängig von den Feldtypen Unpin sein könnten, da das Pinning nicht rekursiv ist?

Ein Anwendungsfall, den ich mir vorstellen kann, ist eine aufdringliche verknüpfte Liste, in der Sie sich nur um die Adresse der gesamten Struktur kümmern, die jedoch eine Reihe schlecht ausgerichteter Daten enthält, die Sie zusammenfassen möchten, ohne sich um die Adresse dieser Daten zu kümmern.

Ich musste die Pin-API aufgrund meiner Arbeit mit Futures lernen und habe eine Frage.

  • Box implementiert bedingungslos Unpin .

  • Box implementiert bedingungslos DerefMut .

  • Die Pin::get_mut -Methode funktioniert immer mit &mut Box<T> (weil Box Unpin bedingungslos implementiert).

Zusammengenommen ermöglicht dies das Verschieben eines angehefteten Werts mit absolut sicherem Code .

Ist das beabsichtigt? Was ist der Grund, warum dies sicher ist?

Es sieht so aus, als hätten Sie Pin<&mut Box<...>> , was Box steckt, nicht das Zeug in Box . Es ist immer sicher, ein Box , da Box niemals Verweise auf seine Position auf dem Stapel speichert.

Was Sie wahrscheinlich wollen, ist Pin<Box<...>> . Spielplatz Link .

EDIT: nicht mehr genau

@Pauan Nur weil ein Box angeheftet ist, heißt das nicht, dass das Ding _inside_ festgesteckt ist. Jeder davon abhängige Code wäre falsch.

Das, wonach Sie suchen, ist wahrscheinlich PinBox , was das von Ihnen erwähnte Verhalten nicht zulässt und es Ihnen ermöglicht, PinMut<Foo> .

@tikue Es ist immer noch möglich, aus einem Pin<Box<...>> herauszukommen, was sie meiner Meinung nach vermeiden wollten .

@tmandry Korrigieren Sie mich, wenn ich falsch Pin<Box<...>> steckt das Ding in das Box , nicht in die Box selbst. In @Pauans ursprünglichem Beispiel hatten sie ein Pin<&mut Box<...>> , das nur die Box feststeckte. Siehe meinen Spielplatz-Link, der zeigt, wie Pin<Box<...>> verhindert, dass ein veränderbarer Verweis auf das Objekt in der Box angezeigt wird.

Beachten Sie, dass PinBox kürzlich entfernt wurde und Pin<Box<T>> jetzt dieselbe Semantik wie PinBox .

@tmandry PinBox<T> wurde entfernt und Pin<Box<T>> Nightly durch Hier ist der richtige Nightly Link.

Oh, die Regeln müssen sich geändert haben, seit ich sie das letzte Mal benutzt habe. Entschuldigung für die Verwirrung.

@tmandry Ja, die Änderungen waren sehr neu. Da die Dinge immer noch im Fluss sind, ist es schwierig, mit allen Änderungen Schritt zu halten.

Der Kommentar von @tikue ist korrekt. Sie müssen sich daran erinnern, dass Pins nur eine Indirektionsebene nach unten stecken.

@tikue @tmandry @withoutboats Danke für die Antworten! Es war sehr hilfreich.

Wie sind die Zustände der beiden Unternehmen jetzt? ( api-refactor & get_mut_unchecked_mut_mut ) Als jemand, der gespannt auf die Funktion der asynchronen / erwarteten Serie wartet, frage ich mich, auf welche rustc-Version die APIs Pin abzielen? Gibt es eine Schätzung?

@ crlf0710 siehe Stabilisierungsvorschlag .

@withoutboats Scheint erledigt? Sollen wir schließen?

Ok, ich entschuldige mich, wenn dies nicht der richtige Ort ist, um dies zu posten, aber ich habe über das Problem Drop + !Unpin nachgedacht und bin auf die folgende Idee gekommen:

  1. Wenn Drop::drop fn(self: Pin<&mut Self>) wäre, gäbe es im Idealfall kein Problem. Nennen wir so ein Drop PinDrop . Wir können Drop nicht einfach durch PinDrop ersetzen, da es Probleme mit der Retrokompatibilität gibt.
  1. Da das einzige Problem mit Drop::drop(&mut self) für den Fall Drop + !Unpin ist, könnten wir ein Standardimplement von PinDrop für Drop + Unpin ableiten (seitdem Pin<&mut T> : DerefMut<Target = T> ) und machen Sie PinDrop zum Merkmal, das von rustc automatisch verwendet wird (dank Pin::new_unchecked(&mut self) , da Drop der einzige Fall von Stack-Pinning ist, wenn wir darüber nachdenken).

Hier ist ein skizzenhafter PoC der Idee: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9aae40afe732babeafef9dab3d7525a8

Wie auch immer, imho sollte dies in beta bleiben und noch nicht stabil bleiben, auch wenn es eine Ausnahme sein muss. Wenn es eine Zeit gibt, in der das Verhalten von Drop von Unpin abhängen kann, ohne die Kompatibilität zu beeinträchtigen, dann ist diese Zeit jetzt.

@danielhenrymantilla Ich sehe nicht, wie das das Problem der Kompatibilität mit vorhandenen generischen Drop-Impls wie impl<T> Drop for Vec<T> löst.

Sie haben Recht, es erfordert eine andere Sache:
ein implizites T: Unpin , das an alle Generika gebunden ist, mit einem Opt-out, bei dem ?Unpin auf die gleiche Weise wie Sized

Das sollte es werden lassen

impl<T> Drop for Vec<T>
where
  T : Unpin, // implicit, which implies that Vec<T> : Unpin which "upgrades" `Drop` into `PinDrop`

ein implizites T: Unpin , das an alle Generika gebunden ist, mit einem Opt-out, bei dem ?Unpin auf die gleiche Weise wie Sized

Dies hat enorme Auswirkungen auf das gesamte API-Design und wurde im Rahmen der ?Move -Vorschläge ausführlich erörtert. Zum Beispiel würde dies bedeuten, dass viele, viele vorhandene Bibliotheken aktualisiert werden müssten, um mit dem Fixieren zu arbeiten. Die Schlussfolgerung war, dass es besser ist, mit einer Lösung nur für Bibliotheken zu arbeiten, wie wir sie jetzt haben, weil sie nichts davon erfordert.

Ja, kurzfristig enorme Kosten, da alle vorhandenen Bibliotheken aktualisiert werden müssten, um !Unpin kompatibel zu sein, aber auf lange Sicht würden wir ein "sichereres" Drop . Anfangs schien es nicht so schlimm zu sein, da wir zumindest nichts kaputt machen.

Aber es ist ein faires Anliegen (ich wusste nicht, dass es zuvor angesprochen wurde; danke, dass Sie darauf hingewiesen haben, @RalfJung ), und ich denke, dass die kurzfristigen praktischen Nachteile den kleinen Sicherheitsgewinn eines drop(Pin<&mut Self>) übersteigen drop(Pin<&mut Self>) .

Gab es eine Diskussion über die Implementierung von Hash für das Hashing von Pin -Typen auf Adressen?

Pin sollte wahrscheinlich eine Implementierung von Hash , die einfach an den enthaltenen Zeiger delegiert. Es gibt keine Priorität für das Hashing basierend auf Adressen (und ich sehe keinen Grund, warum das Fixieren eines Werts die Vorgehensweise ändern sollte es wird gehasht).

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen