Rust: Gewerkschaften ohne Tags (Tracking-Problem für RFC 1444)

Erstellt am 8. Apr. 2016  ·  210Kommentare  ·  Quelle: rust-lang/rust

Tracking-Problem für rust-lang / rfcs # 1444.

Ungelöste Fragen:

  • [x] Wird durch direktes Zuweisen zu einem Vereinigungsfeld ein Tropfen des vorherigen Inhalts ausgelöst?
  • [x] Werden die anderen beim Verlassen eines Gewerkschaftsbereichs als ungültig betrachtet? ( 1 , 2 , 3 , 4 )
  • [] Unter welchen Bedingungen können Sie Copy für eine Gewerkschaft implementieren? Was ist zum Beispiel, wenn einige Varianten vom Typ "Nicht kopieren" sind? Alle Varianten?
  • [] Welche Wechselwirkung besteht zwischen Gewerkschaften und Optimierungen des Enum-Layouts? (https://github.com/rust-lang/rust/issues/36394)

Offene Themen von hoher Bedeutung:

B-RFC-approved B-unstable C-tracking-issue F-untagged_unions T-lang disposition-merge finished-final-comment-period

Hilfreichster Kommentar

@nrc
Nun, die Teilmenge ist ziemlich offensichtlich - "FFI-Gewerkschaften" oder "C-Gewerkschaften" oder "Gewerkschaften vor C ++ 11" - auch wenn sie nicht syntaktisch sind. Mein ursprüngliches Ziel war es, diese Teilmenge so schnell wie möglich zu stabilisieren (idealerweise in diesem Zyklus), damit sie in Bibliotheken wie winapi .
Die verbleibende Teilmenge und ihre Implementierung sind nicht besonders zweifelhaft. Sie ist einfach nicht dringend und muss auf unklare Zeit warten, bis der Prozess für "Unions 1.2" -RFC abgeschlossen ist. Meine Erwartungen wären, die verbleibenden Teile in 1, 2 oder 3 Zyklen nach der Stabilisierung der anfänglichen Teilmenge zu stabilisieren.

Alle 210 Kommentare

Ich habe es vielleicht in der Diskussion über den RFC verpasst, aber bin ich zu Recht der Meinung, dass Destruktoren von Gewerkschaftsvarianten niemals ausgeführt werden? Würde der Destruktor für Box::new(1) in diesem Beispiel ausgeführt?

union Foo {
    f: i32,
    g: Box<i32>,
}

let mut f = Foo { g: Box::new(1) };
f.g = Box::new(2);

@sfackler Mein derzeitiges Verständnis ist, dass f.g = Box::new(2) _ den Destruktor ausführen wird, aber f = Foo { g: Box::new(2) } _nicht_ würde. Das heißt, die Zuweisung zu einem Box<i32> l- Foo l-Wert jedoch nicht.

Eine Zuordnung zu einer Variante ist also wie eine Behauptung, dass das Feld zuvor "gültig" war?

@sfackler Für Drop Typen, ja, das ist mein Verständnis. Wenn sie zuvor nicht gültig waren, müssen Sie das Konstruktorformular Foo oder ptr::write . Auf den ersten Blick scheint es jedoch nicht so zu sein, dass der RFC dieses Detail explizit beschreibt. Ich sehe es als eine Instanziierung der allgemeinen Regel, dass das Schreiben in einen Drop l-Wert einen Destruktoraufruf verursacht.

Sollte eine & mut-Vereinigung mit Drop-Varianten ein Flusen sein?

Am Freitag, den 8. April 2016, schrieb Scott Olson [email protected] :

@sfackler https://github.com/sfackler Für Drop-Typen, ja, das ist meine
Verstehen. Wenn sie vorher nicht gültig waren, müssen Sie das Foo verwenden
Konstruktorform oder ptr :: write. Auf den ersten Blick scheint es nicht so
Der RFC weist jedoch ausdrücklich auf dieses Detail hin.

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment -207634431

Am 8. April 2016, 15:36:22 Uhr PDT, schrieb Scott Olson [email protected] :

@sfackler Für Drop Typen, ja, das ist mein Verständnis. Wenn sie
waren vorher nicht gültig, müssen Sie das Konstruktorformular Foo oder verwenden
ptr::write . Auf den ersten Blick scheint es nicht so zu sein, wie es der RFC ist
explizit über dieses Detail.

Ich hätte diesen Fall ausdrücklich behandeln sollen. Ich denke, beide Verhaltensweisen sind vertretbar, aber ich denke, es wäre weit weniger überraschend, niemals implizit ein Feld fallen zu lassen. Der RFC empfiehlt bereits Flusen für Vereinigungsfelder mit Typen, die Drop implementieren. Ich glaube nicht, dass das Zuweisen zu einem Feld impliziert, dass das Feld zuvor gültig war.

Ja, dieser Ansatz scheint mir auch ein bisschen weniger gefährlich zu sein.

Wenn Sie beim Zuweisen zu einem Gewerkschaftsfeld nicht fallen lassen, verhält sich f.g = Box::new(2) anders als let p = &mut f.g; *p = Box::new(2) , da Sie den letzteren Fall nicht _not_ fallen lassen können. Ich denke, mein Ansatz ist weniger überraschend.

Es ist auch kein neues Problem; unsafe Programmierer müssen sich bereits mit anderen Situationen auseinandersetzen, in denen foo = bar UB ist, wenn foo nicht initialisiert ist und Drop .

Ich persönlich habe nicht vor, Drop-Typen mit Gewerkschaften zu verwenden. Ich werde mich also ganz den Leuten zuwenden, die mit analogem unsicherem Code an der Semantik gearbeitet haben.

Ich habe auch nicht vor, Drop-Typen in Gewerkschaften zu verwenden, daher ist mir beides egal, solange es konsistent ist.

Ich beabsichtige nicht, veränderbare Verweise auf Gewerkschaften zu verwenden, und wahrscheinlich
nur "seltsam getaggte" mit Into

Am Freitag, den 8. April 2016, schrieb Peter Atashian [email protected] :

Ich habe auch nicht vor, Drop-Typen in Gewerkschaften zu verwenden, so oder so nicht
ist mir wichtig, solange es konsequent ist.

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail oder sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment -207653168

Dies scheint ein gutes Thema zu sein, das als ungelöste Frage aufgeworfen werden kann. Ich bin mir noch nicht sicher, welchen Ansatz ich bevorzuge.

@nikomatsakis So sehr ich es als umständlich zuzuweisen , scheint der erwähnte Referenzfall

Und ich möchte noch einmal betonen, dass unsafe Programmierer bereits allgemein wissen müssen, dass a = b drop_in_place(&mut a); ptr::write(&mut a, b) , um sicheren Code zu schreiben. Das Nicht-Löschen von Vereinigungsfeldern wäre eine Ausnahme mehr, nicht eine weniger.

(NB: Der Abfall tritt nicht auf, wenn bekannt ist, dass a bereits nicht initialisiert ist, wie let a; a = b; .)

Aber ich unterstütze eine Standardwarnung vor Drop Varianten in Gewerkschaften, dass die Leute #[allow(..)] da dies ein ziemlich nicht offensichtliches Detail ist.

@tsion Dies gilt nicht für a = b und vielleicht nur manchmal für a.x = b aber es gilt sicherlich für *a = b . Diese Unsicherheit hat mich zögern lassen. Dies kompiliert zum Beispiel:

fn main() {
  let mut x: (i32, i32);
  x.0 = 2;
  x.1 = 3;
}

(obwohl der Versuch, x später zu drucken, fehlschlägt, aber ich halte das für einen Fehler)

@nikomatsakis Dieses Beispiel ist neu für mich. Ich denke, ich hätte es aufgrund meiner bisherigen Erfahrung als Fehler angesehen, den dieses Beispiel kompiliert.

Aber ich bin nicht sicher, ob ich die Relevanz dieses Beispiels sehe. Warum ist das, was ich gesagt habe, nicht wahr für a = b und nur manchmal für a.x = b ?

Angenommen, x.0 hätte einen Typ mit einem Destruktor, dann heißt dieser Destruktor sicherlich:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called before writing new value
}

Vielleicht nur gegen diese Art von Schreiben?

Mein Punkt ist nur, dass = den Destruktor nicht immer ausführt; es
verwendet einige Kenntnisse darüber, ob das Ziel bekannt ist
initialisiert.

Am Dienstag, den 12. April 2016 um 16:10:39 Uhr -0700 schrieb Scott Olson:

@ Nikomatsakis Das Beispiel neu für mich. Ich denke, ich hätte es aufgrund meiner bisherigen Erfahrung als Fehler angesehen, den dieses Beispiel kompiliert.

Aber ich bin nicht sicher, ob ich die Relevanz dieses Beispiels sehe. Warum ist das, was ich gesagt habe, nicht wahr für a = b und nur manchmal für 'ax = b'?

Angenommen, x.0 hätte einen Typ mit einem Destruktor, dann heißt dieser Destruktor sicherlich:

fn main() {
    let mut x: (Box<i32>, i32);
    x.0 = Box::new(2); // x.0 statically know to be uninit, destructor not called
    x.0 = Box::new(3); // x.0 destructor is called
}

@ Nikomatsakis

Der Destruktor wird ausgeführt, wenn das Drop-Flag gesetzt ist.

Aber ich denke, diese Art des Schreibens ist trotzdem verwirrend. Warum also nicht einfach verbieten? Sie können immer *(&mut u.var) = val tun.

Mein Punkt ist nur, dass = den Destruktor nicht immer ausführt; Es verwendet einige Kenntnisse darüber, ob bekannt ist, dass das Ziel initialisiert wird.

@nikomatsakis Ich habe bereits erwähnt, dass:

(NB: Der Abfall tritt nicht auf, wenn a statisch bekannt ist, dass er bereits nicht initialisiert ist, wie z. B. a; a = b;.)

Aber ich habe die dynamische Überprüfung von Drop-Flags nicht berücksichtigt, daher ist dies definitiv komplizierter als ich dachte.

@tsion

Drop-Flags sind nur semi-dynamisch - nachdem der Null-Drop weg ist, sind sie Teil von Codegen. Ich sage, wir verbieten diese Art des Schreibens, weil es mehr Verwirrung als Nutzen bringt.

Sollten Drop Typen überhaupt in Gewerkschaften erlaubt sein? Wenn ich die Dinge richtig verstehe, besteht der Hauptgrund für Gewerkschaften in Rust darin, mit C-Code zusammenzuarbeiten, der Gewerkschaften hat, und C hat nicht einmal Destruktoren. Für alle anderen Zwecke scheint es besser zu sein, nur einen enum im Rust-Code zu verwenden.

Es gibt einen gültigen Anwendungsfall für die Verwendung einer Union zum Implementieren eines NoDrop -Typs, der das Löschen verhindert.

Sowie das manuelle Aufrufen eines solchen Codes über drop_in_place oder ähnliches.

Für mich ist das Löschen eines Feldwerts beim Schreiben definitiv falsch, da der vorherige Optionstyp undefiniert ist.

Wäre es möglich, Feldsetzer zu verbieten, aber einen vollständigen Gewerkschaftsersatz zu verlangen? In diesem Fall würde, wenn die Union Drop implementiert, der vollständige Union Drop für den Wert aufgerufen, der wie erwartet ersetzt wird.

Ich halte es nicht für sinnvoll, Feldsetzer zu verbieten. Die meisten Verwendungszwecke von Gewerkschaften sollten kein Problem damit haben, und Felder ohne Drop-Implementierung bleiben wahrscheinlich der häufigste Fall. Gewerkschaften mit Feldern, die Drop implementieren, erzeugen standardmäßig eine Warnung, wodurch es noch weniger wahrscheinlich ist, dass dieser Fall versehentlich auftritt.

Zur Diskussion beabsichtige ich, veränderbare Verweise auf Felder in Gewerkschaften verfügbar zu machen und beliebige (möglicherweise Drop ) Typen in sie einzufügen. Grundsätzlich möchte ich Gewerkschaften verwenden, um benutzerdefinierte platzsparende Aufzählungen zu schreiben. Zum Beispiel,

union SlotInner<V> {
    next_empty: usize, /* index of next empty slot */
    value: V,
}

struct Slot<V> {
    inner: SlotInner<V>,
    version: u64 /* even version -> is_empty */
}

@nikomatsakis Ich möchte eine konkrete Antwort auf die Frage vorschlagen, die derzeit hier als ungelöst aufgeführt ist.

Um unnötig komplexe Semantiken zu vermeiden, sollte das Zuweisen zu einem Vereinigungsfeld wie das Zuweisen zu einem Strukturfeld erfolgen, was bedeutet, dass der alte Inhalt gelöscht wird. Es ist leicht genug, dies zu vermeiden, wenn Sie davon wissen, indem Sie es stattdessen der gesamten Gewerkschaft zuweisen. Dies ist immer noch ein etwas überraschendes Verhalten, aber ein Vereinigungsfeld, das Drop überhaupt implementiert, erzeugt eine Warnung, und der Text dieser Warnung kann dies ausdrücklich als Einschränkung erwähnen.

Wäre es sinnvoll, eine RFC-Pull-Anforderung zur Änderung von RFC1444 bereitzustellen, um dieses Verhalten zu dokumentieren?

@joshtriplett Da @nikomatsakis im Urlaub ist, werde ich antworten: Ich denke, es ist eine großartige Form, einen RFC-Änderungsantrag zur Lösung Fragen einzureichen . Bei Bedarf haben wir solche RFC-PRs häufig beschleunigt.

@aturon Danke. Ich habe die neue RFC PR https://github.com/rust-lang/rfcs/issues/1663 mit diesen Erläuterungen an RFC1444 gesendet, um dieses Problem zu beheben.

( @aturon Sie können diese ungelöste Frage jetzt

Ich habe eine vorläufige Implementierung in https://github.com/petrochenkov/rust/tree/union.

Status: Implementiert (Modulo-Fehler), PR übermittelt (https://github.com/rust-lang/rust/pull/36016).

@petrochenkov Super ! Sieht soweit gut aus.

Ich bin mir nicht ganz sicher, wie ich Gewerkschaften mit Nicht- Copy -Feldern im Verschiebungsprüfer behandeln soll.
Angenommen, u ist ein initialisierter Wert von union U { a: A, b: B } und jetzt verlassen wir eines der Felder:

1) A: !Copy, B: !Copy, move_out_of(u.a)
Dies ist einfach, u.b wird ebenfalls in den nicht initialisierten Zustand versetzt.
Überprüfung der geistigen Gesundheit: union U { a: T, b: T } sollte sich genau wie struct S { a: T } + Feldalias verhalten.

2) A: Copy, B: !Copy, move_out_of(u.a)
Angeblich sollte u.b noch initialisiert werden, da move_out_of(u.a) einfach ein memcpy und u.b in keiner Weise ändert.

2) A: !Copy, B: Copy, move_out_of(u.a)
Dies ist der seltsamste Fall; angeblich sollten u.b auch in den nicht initialisierten Zustand versetzt werden, obwohl sie Copy . Copy -Werte können nicht initialisiert werden (z. B. let a: u8; ), aber das Ändern ihres Status von initialisiert zu nicht initialisiert ist etwas Neues, AFAIK.

@ retep998
Ich weiß, dass dies für FFI-Bedürfnisse völlig irrelevant ist :)
Die gute Nachricht ist, dass es kein Blocker ist. Ich werde das einfachere Verhalten implementieren und an diesem Wochenende PR einreichen.

@petrochenkov Mein Instinkt ist, dass Gewerkschaften im Wesentlichen ein "Bit-Bucket" sind. Sie sind dafür verantwortlich zu verfolgen, ob die Daten initialisiert sind oder nicht und welcher Typ wahr ist. Dies ist dem Referenten eines Rohzeigers sehr ähnlich.

Aus diesem Grund können wir nicht die Daten für Sie fallen, und warum auch jeder Zugriff auf die Felder nicht sicher ist (auch wenn, sagen wir, gibt es nur eine Variante ist).

Nach diesen Regeln würde ich erwarten, dass Gewerkschaften Copy implementieren, wenn für sie eine Kopie implementiert wird. Im Gegensatz zu Strukturen / Aufzählungen gibt es jedoch keine internen Überprüfungen der Integrität: Sie können jederzeit eine Kopie für einen Unionstyp implementieren, wenn Sie möchten.

Lassen Sie mich einige Beispiele zur Verdeutlichung geben:

union Foo { ... } // contents don't matter

Diese Vereinigung ist affin, da Copy nicht implementiert wurde.

union Bar { x: Rc<String> }
impl Copy for Bar { }
impl Clone for Bar { fn clone(&self) -> Self { *self } }

Dieser Vereinigungstyp Bar ist eine Kopie, da Copy implementiert wurde.

Beachten Sie, dass es aufgrund des Feldtyps x ein Fehler wäre, Copy zu implementieren, wenn Bar eine Struktur wäre.

Huh, ich schätze, ich beantworte Ihre Frage nicht wirklich, jetzt, wo ich sie noch einmal gelesen habe. =)

OK, mir ist klar, dass ich Ihre Frage überhaupt nicht beantwortet habe. Also lass es mich noch einmal versuchen. Nach dem "Bit Bucket" -Prinzip würde ich immer noch erwarten, dass wir nach Belieben aus einer Gewerkschaft austreten können. Aber eine andere Möglichkeit wäre natürlich, es so zu behandeln, als würden wir ein *mut T , und Sie müssen ptr::read , um ausziehen zu können.

EDIT: Ich bin mir nicht ganz sicher, warum wir solche Bewegungen verbieten würden. Möglicherweise musste es mit einem beweglichen Tropfen ausgeführt werden - oder vielleicht nur, weil es leicht ist, einen Fehler zu machen und es besser erscheint, "Auszüge" expliziter zu machen? Ich habe Probleme, mich an die Geschichte hier zu erinnern.

@ Nikomatsakis

Mein Instinkt ist, dass Gewerkschaften im Wesentlichen ein "Bit-Bucket" sind.

Ha, ich möchte im Gegenteil so viele Garantien für den Inhalt der Gewerkschaft wie möglich für ein so gefährliches Konstrukt geben.

Die Interpretation ist, dass Union eine Aufzählung ist, für die wir den Diskriminanten nicht kennen, dh wir können garantieren, dass zu jedem Zeitpunkt mindestens eine der Varianten von Union einen gültigen Wert hat (es sei denn, es handelt sich um unsicheren Code).

Alle Ausleih- / Verschiebungsregeln in der aktuellen Implementierung unterstützen diese Garantie. Gleichzeitig ist dies die konservativste Interpretation, die es uns ermöglicht, entweder den "sicheren" Weg zu gehen (z. B. einen sicheren Zugang zu Gewerkschaften mit gleich typisierten Feldern zu ermöglichen nützlich ) oder der "Bit Bucket" Weg in der Zukunft, wenn mehr Erfahrung mit Rust Gewerkschaften gesammelt wird.

Eigentlich möchte ich es noch konservativer machen, wie unter https://github.com/rust-lang/rust/pull/36016#issuecomment -242810887 beschrieben

@petrochenkov

Die Interpretation ist, dass Union eine Aufzählung ist, für die wir den Diskriminanten nicht kennen, dh wir können garantieren, dass zu jedem Zeitpunkt mindestens eine der Varianten von Union einen gültigen Wert hat (es sei denn, es handelt sich um unsicheren Code).

Beachten Sie, dass bei der Arbeit mit einer Gewerkschaft immer unsicherer Code beteiligt ist, da jeder Zugriff auf ein Feld unsicher ist.

Ich denke, die Art und Weise, wie ich das sehe, ist ähnlich. Grundsätzlich ist eine Union wie eine Aufzählung, kann aber gleichzeitig in mehr als einer Variante vorliegen. Die Menge der gültigen Varianten ist dem Compiler zu keinem Zeitpunkt bekannt, obwohl wir manchmal feststellen können, dass die Menge leer ist (dh die Aufzählung ist nicht initialisiert).

Daher sehe ich jede Verwendung von some_union.field als implizite (und unsichere) Behauptung, dass der Satz gültiger Varianten derzeit field . Dies scheint mit der Funktionsweise der Integration des Kreditprüfers vereinbar zu sein. Wenn Sie das Feld x ausleihen und dann versuchen, y , wird eine Fehlermeldung angezeigt, da Sie im Grunde sagen, dass die Daten gleichzeitig x und y (und es ist ausgeliehen). (Im Gegensatz dazu ist es bei einer regulären Aufzählung nicht möglich, mehr als eine Variante gleichzeitig zu bewohnen, und Sie können dies daran erkennen, wie sich die Ausleihregeln auswirken .)

Der Punkt ist jedenfalls, wenn wir uns von einem Feld einer Union "bewegen", ist die Frage, die wir uns stellen, wohl, ob wir daraus schließen können, dass die Interpretation des Wertes als die anderen Varianten nicht mehr gültig ist. Ich denke, es wäre nicht so schwer, so oder so zu argumentieren. Ich halte das für eine Grauzone.

Die Gefahr, konservativ zu sein, besteht darin, dass wir unsicheren Code ausschließen, der sonst sinnvoll und gültig wäre. Aber ich bin damit einverstanden, enger anzufangen und zu entscheiden, ob ich mich später lockern soll.

Wir sollten die Frage diskutieren, welche Bedingungen erforderlich sind, um Copy in einer Gewerkschaft umzusetzen. Außerdem sollten wir sicherstellen, dass wir eine vollständige Liste dieser oben aufgeführten Grauzonen haben, um sicherzustellen, dass wir sie vor der Stabilisierung ansprechen und dokumentieren!

Grundsätzlich ist eine Union wie eine Aufzählung, kann aber gleichzeitig in mehr als einer Variante vorliegen.

Ein Argument gegen die Interpretation "mehr als eine Variante" ist das Verhalten von Gewerkschaften in konstanten Ausdrücken - für diese Gewerkschaften kennen wir immer die einzelne aktive Variante und können auch nicht auf inaktive Varianten zugreifen, da das Umwandeln zur Kompilierungszeit im Allgemeinen schlecht ist (es sei denn, wir versuchen es) den Compiler in eine Art partiellen Zielemulator zu verwandeln).
Meine Interpretation ist, dass inaktive Varianten zur Laufzeit immer noch inaktiv sind, aber zugegriffen werden können, wenn sie mit der aktiven Variante der Union (restriktivere Definition) oder vielmehr mit dem Fragmentzuweisungsverlauf der Union (vager, aber nützlicher) kompatibel sind.

Wir sollten sicherstellen, dass wir eine vollständige Liste dieser Grauzonen haben

Ich werde den Gewerkschafts-RFC in einer nicht allzu fernen Zukunft ändern! Die "Enum" -Interpretation hat ziemlich lustige Konsequenzen.

Das Umwandeln zur Kompilierungszeit ist im Allgemeinen schlecht (es sei denn, wir versuchen, den Compiler in eine Art partiellen Zielemulator zu verwandeln).

@petrochenkov Dies ist eines der Ziele meines Miri- Projekts. Miri kann bereits Transmutationen und verschiedene rohe Zeigershenanigans ausführen. Es wäre ein kleiner Arbeitsaufwand, Miri dazu zu bringen, mit Gewerkschaften umzugehen (nichts Neues auf der Seite der Rohspeicherbehandlung).

Und @eddyb drängt darauf, die konstante Bewertung von rustc durch eine Version von Miri zu ersetzen.

@petrochenkov

Ein Argument gegen die Interpretation "mehr als eine Variante" ist, wie sich Gewerkschaften in konstanten Ausdrücken verhalten ...

Es ist eine interessante Frage, wie die Verwendung von Gewerkschaften in Konstanten am besten unterstützt werden kann, aber ich sehe kein Problem darin, konstante Ausdrücke auf eine Teilmenge des Laufzeitverhaltens zu beschränken (das tun wir sowieso immer). Das heißt, nur weil wir eine bestimmte Übertragung zur Kompilierungszeit möglicherweise nicht vollständig unterstützen können, heißt das nicht, dass sie zur Laufzeit illegal ist.

Meine Interpretation ist, dass inaktive Varianten zur Laufzeit noch inaktiv sind, aber zugegriffen werden können, wenn sie mit der aktiven Variante der Union kompatibel sind

Hmm, ich versuche zu überlegen, wie sich das von der Aussage unterscheidet, dass die Gewerkschaft gleichzeitig zu all diesen Varianten gehört. Ich sehe noch keinen wirklichen Unterschied. :) :)

Ich habe das Gefühl, dass diese Interpretation seltsame Wechselwirkungen mit Bewegungen im Allgemeinen hat. Wenn die Daten beispielsweise "wirklich" ein X sind und Sie sie als Y interpretieren, Y jedoch affin ist, handelt es sich dann immer noch um ein X?

Unabhängig davon denke ich, dass es in Ordnung ist, wenn ein Umzug eines Feldes die gesamte Gewerkschaft verbraucht, kann dies als mit jeder dieser Interpretationen vereinbar angesehen werden. Beim Ansatz "Satz von Varianten" besteht die Idee lediglich darin, dass durch das Verschieben des Werts alle vorhandenen Varianten deinitialisiert werden (und natürlich muss die von Ihnen verwendete Variante eine der gültigen Mengen sein). In Ihrer Version scheint es in diese Variante "umzuwandeln" (und das Original zu verbrauchen).

Ich werde den Gewerkschafts-RFC in einer nicht allzu fernen Zukunft ändern! Die "Enum" -Interpretation hat ziemlich lustige Konsequenzen.

Solches Vertrauen! Du wirst es versuchen;)

Möchten Sie noch ein paar Details darüber erfahren, welche konkreten Änderungen Sie vorhaben?

Möchten Sie noch ein paar Details darüber erfahren, welche konkreten Änderungen Sie vorhaben?

Detailliertere Beschreibung der Implementierung (dh bessere Dokumentation), einige kleine Erweiterungen (wie leere Gewerkschaften und .. in Gewerkschaftsmustern), zwei wichtige (widersprüchliche) Alternativen der Gewerkschaftsentwicklung - unsicherer und weniger restriktiver "Arbeitsbereich" Interpretation und sicherere und restriktivere Interpretation von "Aufzählung mit unbekannter Diskriminanz" - und ihre Konsequenzen für die Verschiebungs- / Initialisierungsprüfung, Copy Impls, unsafe Typ des Feldzugriffs usw.

Es wäre auch nützlich zu definieren, wann der Zugriff auf ein inaktives Vereinigungsfeld UB ist, z

union U { a: u8, b: () }
let u = U { b: () };
let a = u.a; // most probably an UB, equivalent to reading from `mem::uninitialized()`

Dies ist jedoch ein unendlich kniffliger Bereich.

Klingt wahrscheinlich, feldübergreifende Semantik ist im Grunde ein Zeiger, oder?
_ (_ () als * u8)

Am Donnerstag, 1. September 2016, Vadim Petrochenkov [email protected]
schrieb:

Es wäre auch nützlich zu definieren, wenn auf ein inaktives Gewerkschaftsfeld zugegriffen wird
ist UB, z

Vereinigung U {a: u8, b: ()}
sei u = U {b: ()};
sei a = ua; // höchstwahrscheinlich ein UB, entspricht dem Lesen von mem::uninitialized()

Dies ist jedoch ein unendlich kniffliger Bereich.

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment -244154751,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/ABxXhi68qRITTFW5iJn6omZQQBQgzweNks5qlw4qgaJpZM4IDXsj
.

Ist der Feldzugriff nicht immer unsicher?

Am Donnerstag, 1. September 2016, Vadim Petrochenkov [email protected]
schrieb:

Möchten Sie noch ein paar Details darüber erfahren, welche konkreten Änderungen Sie vorhaben?

Detailliertere Beschreibung der Implementierung (dh besser
Dokumentation), einige kleine Erweiterungen (wie leere Gewerkschaften und .. in Union
Muster), zwei wichtige (widersprüchliche) Alternativen der Gewerkschaftsentwicklung - mehr
unsichere und weniger restriktive "Scratch Space" -Interpretation und sicherer
und restriktivere "Aufzählung mit unbekannter Diskriminanz" Interpretation - und
ihre Konsequenzen für die Verschiebungs- / Initialisierungsprüfung, Kopierimplikationen, Unsicherheit
des Feldzugangs usw.

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment -244151164,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/ABxXhuHStN8AFhR3KYDU27U29MiMpN5Bks5qlws9gaJpZM4IDXsj
.

Ist der Feldzugriff nicht immer unsicher?

Es kann manchmal sicher gemacht werden, z

  • Die Zuordnung zu trivial zerstörbaren Vereinigungsfeldern ist sicher.
  • Jeder Zugriff auf Felder eines union U { f1: T, f2: T, ..., fN: T } (dh alle Felder haben den gleichen Typ) ist in der Interpretation "Aufzählung mit unbekannter Diskriminante" sicher.

Aus Anwendersicht erscheint es besser, hier keine besonderen Bedingungen anzuwenden. Nennen Sie es einfach immer unsicher.

Derzeit testen wir die Unterstützung für Gewerkschaften im neuesten Rustc von Git. Alles, was ich versucht habe, funktioniert perfekt.

Ich bin auf einen interessanten Fall im Dead Field Checker gestoßen. Versuchen Sie den folgenden Code:

#![feature(untagged_unions)]

union U {
    i: i32,
    f: f32,
}

fn main() {
    println!("{}", std::mem::size_of::<U>());
    let u = U { f: 1.0 };
    println!("{:#x}", unsafe { u.i });
}

Sie erhalten diesen Fehler:

warning: struct field is never used: `f`, #[warn(dead_code)] on by default

Es sieht so aus, als hätte der dead_code-Prüfer die Initialisierung nicht bemerkt.

(Ich habe bereits PR # 36252 über die Verwendung von "struct field" eingereicht und es in "field" geändert.)

Gewerkschaften können derzeit keine Felder mit dynamischer Größe enthalten, aber der RFC gibt dieses Verhalten in keiner Weise an:

#![feature(untagged_unions)]

union Foo<T: ?Sized> {
  value: T,
}

Ausgabe:

error[E0277]: the trait bound `T: std::marker::Sized` is not satisfied
 --> <anon>:4:5
  |
4 |     value: T,
  |     ^^^^^^^^ trait `T: std::marker::Sized` not satisfied
  |
  = help: consider adding a `where T: std::marker::Sized` bound
  = note: only the last field of a struct or enum variant may have a dynamically sized type

Das kontextbezogene Schlüsselwort funktioniert nicht außerhalb des Modul- / Kistenstammkontexts:

fn main() {
    // all work
    struct Peach {}
    enum Pineapple {}
    trait Mango {}
    impl Mango for () {}
    type Strawberry = ();
    fn woah() {}
    mod even_modules {
        union WithUnions {}
    }
    use std;

    // does not work
    union Banana {}
}

Scheint wie eine ziemlich böse Konsistenzwarze.

@ Nagisa
Verwenden Sie versehentlich eine ältere Version von rustc?
Ich habe gerade Ihr Beispiel auf Laufstall überprüft und es funktioniert (Modulo "leere Vereinigung" Fehler).
Es gibt auch einen Run-Pass-Test, der diese spezielle Situation überprüft - https://github.com/rust-lang/rust/blob/master/src/test/run-pass/union/union-backcomp.rs.

@petrochenkov ah, ich habe play.rlo verwendet, aber es scheint, als wäre es zu stable oder so zurückgekehrt. Also mach mir nichts aus.

Ich denke, die Gewerkschaften müssen irgendwann sichere Felder unterstützen, die bösen Zwillinge unsicherer Felder aus diesem Vorschlag .
cc https://github.com/rust-lang/rfcs/issues/381#issuecomment -246703410

Ich denke, es wäre sinnvoll, eine Möglichkeit zu haben, "sichere Gewerkschaften" anhand verschiedener Kriterien zu erklären.

Beispielsweise scheint eine Vereinigung, die ausschließlich Nicht-Drop-Felder kopieren, alle mit derselben Größe, sicher zu sein. Unabhängig davon, wie Sie auf die Felder zugreifen, werden möglicherweise unerwartete Daten angezeigt, es kann jedoch kein Speichersicherheitsproblem oder undefiniertes Verhalten auftreten.

@joshtriplett Sie müssten auch sicherstellen, dass die Typen, aus denen Sie nicht initialisierte Daten lesen können, keine "Löcher" aufweisen. Wie ich gerne sagen möchte: "Nicht initialisierte Daten sind entweder unvorhersehbare Zufallsdaten oder Ihr privater SSH-Schlüssel, je nachdem, welcher Wert schlechter ist."

Ist &T kopieren und nicht löschen? Setzen Sie das in eine "sichere" Vereinigung mit usize , und Sie haben einen betrügerischen Referenzgenerator. Die Regeln müssen also etwas strenger sein.

Beispielsweise scheint eine Vereinigung, die ausschließlich Nicht-Drop-Felder kopieren, alle mit derselben Größe, sicher zu sein. Unabhängig davon, wie Sie auf die Felder zugreifen, werden möglicherweise unerwartete Daten angezeigt, es kann jedoch kein Speichersicherheitsproblem oder undefiniertes Verhalten auftreten.

Mir ist klar, dass dies nur ein Beispiel von der Stange ist, kein ernsthafter Vorschlag, aber hier sind einige Beispiele, um zu veranschaulichen, wie schwierig dies ist:

  • u8 und bool haben dieselbe Größe, aber die meisten u8 -Werte sind für bool ungültig. Wenn Sie dies ignorieren, wird UB ausgelöst
  • &T und &U haben die gleiche Größe und sind Copy + !Drop für alle T und U (solange beide oder keine Sized )
  • Das Umwandeln zwischen uN / iN und fN ist derzeit nur in unsicherem Code möglich. Ich glaube, solche Transmutationen sind immer sicher, aber dies erweitert die sichere Sprache, so dass sie kontrovers sein kann.
  • Die Verletzung der Privatsphäre (z. B. zwischen struct Foo(Bar); und Bar ) ist ein großes Nein Nein, da die Privatsphäre dazu verwendet werden kann, sicherheitsrelevante Invarianten aufrechtzuerhalten.

@Amanieu Als ich das schrieb, wollte ich eine Notiz darüber

@cuviper Ich habe versucht, "einfache alte Daten" zu definieren, wie in etwas, das Nullzeiger enthält. Sie haben Recht, die Definition müsste Verweise ausschließen. Wahrscheinlich ist es einfacher, eine Reihe zulässiger Typen und Kombinationen dieser Typen auf die Whitelist zu setzen.

@rkruppe

u8 und bool haben die gleiche Größe, aber die meisten u8-Werte sind für bool ungültig, und wenn dies ignoriert wird, wird UB ausgelöst

Guter Punkt; Das gleiche Problem gilt für Aufzählungen.

& T und & U haben die gleiche Größe und sind Copy +! Drop für alle T und U (solange beide oder keine Größe haben)

Das hatte ich vergessen.

Die Übertragung zwischen uN / iN und fN ist derzeit nur in unsicherem Code möglich. Ich glaube, solche Transmutationen sind immer sicher, aber dies erweitert die sichere Sprache, so dass sie kontrovers sein kann.

In beiden Punkten einverstanden; dies scheint akzeptabel zu sein.

Die Verletzung der Privatsphäre (z. B. das Punning zwischen struct Foo (Bar) und Bar) ist ein großes Nein Nein, da die Privatsphäre dazu verwendet werden kann, sicherheitsrelevante Invarianten aufrechtzuerhalten.

Wenn Sie die Interna des Typs nicht kennen, können Sie nicht wissen, ob die Interna die Anforderungen erfüllen (z. B. keine interne Polsterung). Sie können dies also ausschließen, indem Sie verlangen, dass alle Komponenten rekursiv einfache alte Daten sind und dass Sie über eine ausreichende Sichtbarkeit verfügen, um dies zu überprüfen.

Die Übertragung zwischen uN / iN und fN ist derzeit nur in unsicherem Code möglich. Ich glaube, solche Transmutationen sind immer sicher, aber dies erweitert die sichere Sprache, so dass sie kontrovers sein kann.

Gleitkommazahlen haben NaN signalisiert, was eine Trap-Darstellung ist, die zu UB führt.

@ retep998 Läuft Rust auf Plattformen, die das Deaktivieren von Gleitkomma-Traps nicht unterstützen? (Das ändert nichts am UB-Problem, aber theoretisch könnten wir dieses Problem beseitigen.)

@petrochenkov

Die Interpretation ist, dass Union eine Aufzählung ist, für die wir den Diskriminanten nicht kennen, dh wir können garantieren, dass zu jedem Zeitpunkt mindestens eine der Varianten der Union einen gültigen Wert hat

Ich glaube, ich bin zu dieser Interpretation gekommen - nun, nicht genau dazu. Ich denke immer noch daran, dass es eine Reihe von rechtlichen Varianten gibt, die an dem Punkt festgelegt werden, an dem Sie speichern, wie ich es immer getan habe. Ich denke, einen Wert in einer Union zu speichern, als würde man ihn in einen "Quantenzustand" versetzen - er könnte jetzt möglicherweise in eine von vielen rechtlichen Auslegungen umgewandelt werden. Aber ich stimme zu, dass Sie, wenn Sie von einer dieser Varianten ausgehen, sie in nur eine dieser Varianten "gezwungen" und den Wert verbraucht haben. Daher sollten Sie die Aufzählung nicht erneut verwenden können (wenn dieser Typ nicht Copy ). Also 👍 im Grunde.

Frage zu #[repr(C)] : Wie @pnkfelix mir kürzlich x zu speichern und zu lesen, wenn eine Gewerkschaft nicht #[repr(C)] ist mit Feld y . Vermutlich liegt dies daran, dass wir nicht alle Felder mit demselben Versatz starten müssen.

Darin sehe ich einen Nutzen: Beispielsweise kann ein Desinfektionsprogramm Gewerkschaften implementieren, indem er sie wie eine normale Aufzählung (oder sogar eine Struktur ...?) Speichert und überprüft, ob Sie dieselbe Variante verwenden, die Sie eingegeben haben.

Aber es scheint eine Art Fußwaffe zu sein, und auch eine dieser Repräsentantengarantien, die wir in der Praxis niemals tatsächlich ändern könnten, weil sich zu viele Menschen in freier Wildbahn darauf verlassen werden.

Gedanken?

@ Nikomatsakis

Die Interpretation ist, dass Union eine Aufzählung ist, für die wir den Diskriminanten nicht kennen, dh wir können garantieren, dass zu jedem Zeitpunkt mindestens eine der Varianten der Union einen gültigen Wert hat

Das Schlimmste sind Varianten- / Feldfragmente, die für Gewerkschaften direkt zugänglich sind.
Betrachten Sie diesen Code:

union U {
    a: (u8, bool),
    b: (bool, u8),
}
fn main() {
    unsafe {
        let mut u = U { a: (2, false) };
        u.b.1 = 2; // turns union's memory into (2, 2)
    }
}

Alle Felder sind Copy , es ist kein Besitz beteiligt und der Verschiebungsprüfer ist zufrieden, aber die teilweise Zuordnung zum inaktiven Feld b verwandelt die Union in einen Zustand mit 0 gültigen Varianten. Ich habe noch nicht darüber nachgedacht, wie ich damit umgehen soll. Solche Aufgaben machen UB? Interpretation ändern? Etwas anderes?

@petrochenkov

Solche Aufgaben machen UB?

Das wäre meine Annahme, ja. Wenn Sie a zugewiesen haben, war die Variante b nicht in der Menge der gültigen Varianten enthalten, und daher ist die spätere Verwendung von u.b.1 (ob gelesen oder zugewiesen) ungültig.

Frage zu # [repr (C)]: Wie @pnkfelix mir kürzlich

Ich denke, der passende Wortlaut hier ist, dass 1) das Lesen von Feldern, die nicht "layoutkompatibel" (dies ist vage) mit zuvor geschriebenen Feldern / Feldfragmenten sind, UB ist. 2) Für #[repr(C)] Gewerkschaften wissen Benutzer, was Layouts sind (aus ABI-Dokumenten), damit sie zwischen UB und Nicht-UB unterscheiden können 3) Für #[repr(Rust)] Gewerkschaftslayouts nicht spezifiziert, sodass Benutzer nicht sagen können, was UB ist und was nicht, sondern WIR (rustc / libstd + ihre) Tests) haben dieses heilige Wissen, so dass wir die Spreu vom Weizen trennen und #[repr(Rust)] auf Nicht-UB-Weise verwenden können.

4) Nachdem Fragen zur Größen- / Schritt- und Feldumordnung entschieden wurden, würde ich erwarten, dass Struktur- und Gewerkschaftslayouts in Stein gemeißelt und spezifiziert werden, damit Benutzer auch die Layouts kennen und #[repr(Rust)] Gewerkschaften verwenden können so frei wie #[repr(C)] und das Problem wird verschwinden.

@nikomatsakis In der Diskussion über den Gewerkschafts-RFC wurde erwähnt, dass nativer Rust-Code

Gibt es etwas, das Menschen davon abhält, #[repr(C)] ? Wenn nicht, dann sehe ich keine Notwendigkeit, irgendwelche Garantien für #[repr(Rust)] . Lassen Sie es einfach als "hier sind Drachen". Wahrscheinlich ist es am besten, einen Fussel zu haben, der standardmäßig für Gewerkschaften gewarnt wird, die nicht #[repr(C)] .

@ retep998 Es scheint mir vernünftig, dass repr(Rust) kein bestimmtes Layout oder eine bestimmte Überlappung garantiert. Ich würde nur vorschlagen, dass repr(Rust) in der Praxis nicht gegen die Annahmen der Menschen über die Speichernutzung einer Gewerkschaft verstoßen sollte ("nicht größer als das größte Mitglied").

Läuft Rust auf Plattformen, die das Deaktivieren von Gleitkomma-Traps nicht unterstützen?

Das ist keine wirklich gültige Frage. Zunächst kann sich der Optimierer selbst auf die UB-Ness von Trap-Darstellungen verlassen und das Programm auf unerwartete Weise neu schreiben. Darüber hinaus unterstützt Rust die Änderung der FP-Umgebung auch nicht wirklich.

Aber es scheint eine Art Fußwaffe zu sein, und auch eine dieser Repräsentantengarantien, die wir in der Praxis niemals ändern könnten, weil sich zu viele Menschen in freier Wildbahn darauf verlassen werden.

Gedanken?

Das Hinzufügen eines Flusens oder eines solchen, der den Programmablauf überprüft und eine Beschwerde beim Benutzer auslöst, wenn das Lesen aus einem Feld erfolgt, in dem die Aufzählung nachweislich in einem anderen Feld geschrieben wurde, würde dabei helfen¹. MIR-basierte Flusen würden eine kurze Arbeit daraus machen. Wenn eine CFG keine Rückschlüsse auf die Rechtmäßigkeit der Gewerkschaftsfeldlast zulässt und der Benutzer einen Fehler macht, ist das undefinierte Verhalten das beste, das wir angeben können, ohne die Rust-Repräsentation selbst IMO angegeben zu haben.

¹: Besonders effektiv, wenn Menschen aus irgendeinem Grund anfangen, Gewerkschaft als Transmute eines armen Mannes zu verwenden.

sollte in der Praxis nicht gegen die Annahmen der Menschen über die Speichernutzung einer Gewerkschaft verstoßen ("nicht größer als das größte Mitglied").

Ich stimme dir nicht zu. Es kann sehr sinnvoll sein, beispielsweise bei einigen Architekturen repr(Rust) auf die Größe eines Maschinenworts zu erweitern.

Ein Problem, das vor der Stabilisierung möglicherweise berücksichtigt werden sollte, ist https://github.com/rust-lang/rust/issues/37479. Es sieht so aus, als ob mit der neuesten Version von LLDB Debugging-Gewerkschaften möglicherweise nicht funktionieren :(

@alexcrichton Funktioniert es mit GDB?

Soweit ich das beurteilen kann, ja. Die Linux-Bots scheinen den Test einwandfrei auszuführen.

Dann bedeutet dies, dass Rust die richtigen Debugging-Informationen bereitstellt und LLDB hier nur einen Fehler hat. Ich denke nicht, dass ein Fehler in einem von mehreren Debuggern, der in einem anderen nicht vorhanden ist, die Stabilisierung blockieren sollte. LLDB muss nur repariert werden.

Es wäre cool zu sehen, ob wir diese Funktion für den 1.17-Zyklus (das ist die Beta vom 16. März) in FCP integrieren könnten. Kann jemand eine Zusammenfassung der offenen Fragen und der aktuellen Situation des Features geben, damit wir sehen können, ob wir einen Konsens erzielen und alles lösen können?

@ohne Boote
Meine Pläne sind

  • Warten Sie auf die bevorstehende Veröffentlichung (3. Februar).
  • Schlagen Sie eine Stabilisierung der Gewerkschaften mit Copy -Feldern vor. Dies wird alle FFI-Anforderungen abdecken - FFI-Bibliotheken können Gewerkschaften auf Stable verwenden. "POD" -Unionen werden seit Jahrzehnten in C / C ++ verwendet und sind gut bekannt (Modulo-basiertes Aliasing, aber Rust hat es nicht). Es sind auch keine Blocker bekannt.
  • Schreiben Sie "Unions 1.2" RFC bis zum 3. Februar. Es beschreibt die aktuelle Implementierung von Gewerkschaften und skizziert zukünftige Richtungen. Die Zukunft von Gewerkschaften mit Nicht- Copy -Feldern wird im Zuge der Erörterung dieses RFC entschieden.

Beachten Sie, dass das Offenlegen von ManuallyDrop oder NoDrop aus der Standardbibliothek keine Stabilisierung der Gewerkschaften erfordert.

STATUS-UPDATE (4. Februar): Ich schreibe den RFC, aber ich habe wie üblich nach jedem Satz eine Schreibblockade, daher besteht die Möglichkeit, dass ich ihn am nächsten Wochenende (11.-12. Februar) und nicht an diesem Wochenende beende (4. bis 5. Februar).
STATUS-UPDATE (11. Februar): Der Text ist zu 95% fertig, ich werde ihn morgen einreichen.

@petrochenkov das scheint eine sehr vernünftige Vorgehensweise zu sein.

@petrochenkov Das klingt für mich vernünftig. Ich habe auch Ihren Vorschlag für Gewerkschaften 1.2 geprüft und einige Kommentare abgegeben. Insgesamt sieht es für mich gut aus.

@joshtriplett Ich dachte, während wir in der @ rust-lang / lang-Besprechung darüber sprachen, die

Daher habe ich den RFC "Unions 1.2" eingereicht - https://github.com/rust-lang/rfcs/pull/1897.

Jetzt möchte ich die Stabilisierung einer konservativen Teilmenge der Union vorschlagen - alle Felder der Union sollten Copy , die Anzahl der Felder sollte ungleich Null sein und die Union sollte nicht Drop implementieren .
(Ich bin mir jedoch nicht sicher, ob die letzte Anforderung realisierbar ist, da sie leicht umgangen werden kann, indem die Union in eine Struktur eingeschlossen und Drop für diese Struktur implementiert wird.)
Solche Gewerkschaften decken alle Bedürfnisse von FFI-Bibliotheken ab, die der Hauptverbraucher dieser Sprachfunktion sein sollen.

Der Text von "Unions 1.2" RFC sagt nichts Neues über Gewerkschaften im FFI-Stil aus, außer dass ausdrücklich bestätigt wird, dass Typ-Punning zulässig ist.
BEARBEITEN : "Unions 1.2" RFC wird auch Zuweisungen zu trivial zerstörbaren Copy -Feldern sicher machen (siehe https://github.com/rust-lang/rust/issues/32836#issuecomment-281296416, https : //github.com/rust-lang/rust/issues/32836#issuecomment-281748451) betrifft dies auch Gewerkschaften im FFI-Stil.

Dieser Text enthält auch die für die Stabilisierung erforderliche Dokumentation.
Der Abschnitt "Übersicht" kann in das Buch und "Detailliertes Design" in die Referenz eingefügt werden.

ping @nikomatsakis

Muss so etwas wirklich als Teil der Sprache hinzugefügt werden? Ich habe ungefähr 20 Minuten gebraucht, um eine Implementierung einer Gewerkschaft mit ein wenig unsafe und ptr::write() .

use std::mem;
use std::ptr;


/// A union of `f64`, `bool`, and `i32`.
#[derive(Default, Clone, PartialEq, Debug)]
struct Union {
    data: [u8; 8],
}

impl Union {
    pub unsafe fn get<T>(&self) -> &T {
        &*(&self.data as *const _ as *const T)
    }

    pub unsafe fn set<T>(&mut self, value: T) {
        // "transmute" our pointer to self.data into a &mut T so we can 
        // use ptr::write()
        let data_ptr: &mut T = &mut *(&mut self.data as *mut _ as *mut T);
        ptr::write(data_ptr, value);
    }
}


fn main() {
    let mut u = Union::default();
    println!("data: {0:?} ({0:#p})", &u.data);
    {
        let as_i32: &i32 = unsafe { u.get() };
        println!("as i32: {0:?} ({0:#p})", as_i32);
    }

    unsafe {
        u.set::<f64>(3.14);
    }

    println!("As an f64: {:?}", unsafe { u.get::<f64>() });
}

Ich denke, es wäre nicht schwierig für jemanden, ein Makro zu schreiben, das so etwas erzeugen kann, außer sicherzustellen, dass das interne Array die Größe des größten Typs hat. Anstelle meiner völlig generischen (und schrecklich unsicheren) get::<T>() könnten sie dann ein Merkmal hinzufügen, das die Arten einschränkt, die Sie erhalten und festlegen können. Sie können sogar bestimmte Getter- und Setter-Methoden hinzufügen, wenn Sie benannte Felder möchten.

Ich denke, sie könnten so etwas schreiben:

union! { Foo(u64, Vec<u8>, String) };

Mein Punkt ist, dass dies als Teil einer Bibliothek durchaus machbar ist, anstatt einer bereits recht komplexen Sprache zusätzliche Syntax und Komplexität hinzuzufügen. Außerdem ist es mit Proc-Makros bereits durchaus möglich, auch wenn dies noch nicht vollständig stabil ist.

@ Michael-F-Bryan Wir haben aber noch keine konstante size_of .

@ Michael-F-Bryan Es reicht nicht nur aus, ein [u8] -Array zu haben, sondern Sie müssen auch die richtige Ausrichtung erhalten . Tatsächlich verwende ich bereits Makros, um mit Gewerkschaften umzugehen, aber aufgrund des Fehlens der Konstanten size_of und align_of ich den richtigen Speicherplatz manuell zuweisen, und weil deklarative Makros I keine verwendbare Ident-Verkettung enthalten müssen die Namen sowohl für die Getter als auch für die Setter manuell angeben. Selbst das Initialisieren einer Union ist im Moment schwierig, da ich sie zuerst mit einem Standardwert initialisieren und dann den Wert auf die gewünschte Variante setzen muss (oder einen anderen Satz von Methoden hinzufügen muss, um die Union zu erstellen, die in der Definition noch ausführlicher ist der Gewerkschaft). Es ist insgesamt viel mehr Arbeit und fehleranfällig und hässlicher als die native Unterstützung für Gewerkschaften. Vielleicht sollten Sie den RFC und die dazugehörige Diskussion lesen, damit Sie verstehen, warum diese Funktion so wichtig ist.

Und das Gleiche gilt für die Ausrichtung.

Ich stelle mir vor, dass die Verkettung von Identitäten nicht allzu schwierig sein sollte, jetzt existiert syn . Sie können damit Operationen an dem übergebenen AST ausführen , sodass Sie zwei Ident implementiert AsRef<str> ) und dann ein neues Ident erstellen können ist die Verkettung der beiden mit Ident::From<String>() .

Der RFC erwähnt viel darüber, wie umständlich die Verwendung vorhandener Makroimplementierungen ist. Mit der jüngsten Erstellung von Kisten wie syn und quote ist es jetzt viel einfacher, Proc-Makros zu erstellen. Ich bin der Meinung, dass dies einen großen Beitrag zur Verbesserung der Ergonomie und zur Verringerung der Fehleranfälligkeit leisten würde.

Zum Beispiel könnten Sie ein MyUnion::default() das nur den internen Puffer der Gewerkschaft auf Null setzt, und dann ein fn MyUnion::new<T>(value:T) -> MyUnion , wobei T ein Merkmal gebunden ist, das sicherstellt, dass Sie nur mit den richtigen Typen initialisieren können .

Können Sie in Bezug auf Ausrichtung und Größe das Modul mem aus der Standardbibliothek verwenden (dh std :: mem :: align_of () und Freunde)? Ich denke, alles, was ich vorschlage, würde davon abhängen, ob ich diese zur Makro-Expansionszeit verwenden kann, um die erforderliche Größe und Ausrichtung herauszufinden. In 99,9% der Fälle, in denen Gewerkschaften verwendet werden, werden ohnehin primitive Typen verwendet. Ich glaube, Sie könnten eine Hilfsfunktion schreiben, die den Namen eines Typs übernimmt und dessen Ausrichtung oder Größe zurückgibt (möglicherweise den Compiler fragen, obwohl dies mehr ist ein Implementierungsdetail).

Ich gebe zu, ein eingebauter Mustervergleich wäre sehr schön, aber die meisten Gewerkschaften, die Sie in FFI verwenden, werden sowieso in eine dünne Abstraktionsschicht gehüllt. So können Sie möglicherweise mit ein paar if / else-Anweisungen oder einer Hilfsfunktion davonkommen.

Können Sie in Bezug auf Ausrichtung und Größe das Mem-Modul aus der Standardbibliothek verwenden (dh std :: mem :: align_of () und Freunde)?

Das wird in keinem Cross-Compilation-Kontext funktionieren.

@ Michael-F-Bryan Alle diese und viele weitere Diskussionen wurden in der Geschichte von https://github.com/rust-lang/rfcs/pull/1444 geführt . Um die Antworten auf Ihre spezifischen Bedenken zusammenzufassen, zusätzlich zu den bereits erwähnten: Sie müssten die @ retep998 tatsächlich getan hat) ausgiebig für Windows-Bindungen und kann für die Unbeholfenheit von) bürgen. Außerdem funktionieren Proc-Makros derzeit nur zum Ableiten. Sie können die Syntax nicht an anderer Stelle erweitern.

Ebenfalls:

99,9% der Fälle, in denen Gewerkschaften verwendet werden, werden ohnehin mit primitiven Typen durchgeführt

Überhaupt nicht wahr. C-Code verwendet häufig ein Muster "Struktur von Vereinigungen von Strukturen", bei dem die meisten Vereinigungsfelder aus verschiedenen Strukturtypen bestehen.

@rfcbot fcp merge per @petrochenkovs Kommentar https://github.com/rust-lang/rust/issues/32836#issuecomment -279256434

Ich habe nichts hinzuzufügen, nur den Bot auszulösen

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

  • [x] @aturon
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @ohne Boote

Derzeit sind keine Bedenken aufgeführt.

Sobald diese Prüfer einen Konsens erreicht haben, tritt dies in die endgültige Kommentierungsphase ein. Wenn Sie ein großes Problem entdecken, das zu keinem Zeitpunkt in diesem Prozess angesprochen wurde, melden Sie sich bitte!

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

PSA: Ich werde den RFC "Unions 1.2" mit einer weiteren Änderung aktualisieren, die sich auf Gewerkschaften im FFI-Stil auswirkt. Ich werde sichere Zuweisungen in trivial zerstörbare Gewerkschaftsfelder von "Future Richtungen" auf den eigentlichen RFC verschieben.

union.trivially_destructible_field = 10; // safe

Warum:

  • Zuweisungen zu trivial zerstörbaren Gewerkschaftsfeldern sind unabhängig von der Auslegung der Gewerkschaften unbedingt sicher.
  • Es wird ungefähr die Hälfte der gewerkschaftsbezogenen unsafe -Blöcke entfernt.
  • Aufgrund der potenziell großen Anzahl von unused_unsafe Warnungen / Fehlern im stabilen Code wird dies später schwieriger.

@petrochenkov Meinst du "trivial zerstörbare Gewerkschaftsfelder" oder "Gewerkschaften mit völlig trivial zerstörbaren Feldern"?

Schlagen Sie vor, dass das gesamte unsichere Verhalten beim Lesen auftritt, wenn Sie eine Interpretation auswählen? Zum Beispiel eine Union mit einer Aufzählung und anderen Feldern, in der der Union-Wert eine ungültige Diskriminante enthält?

Auf hohem Niveau erscheint das plausibel. Es erlaubt einige Dinge, die ich als unsicher betrachten würde, aber Rust im Allgemeinen nicht, wie das Umgehen von Destruktoren oder das Auslaufen von Speicher. Auf einem niedrigen Niveau würde ich zögern, diesen Klang in Betracht zu ziehen.

Ich finde es in Ordnung, diese Teilmenge zu stabilisieren. Ich kenne meine Meinung zu diesem RFC von Unions 1.2 noch nicht, da ich keine Zeit hatte, ihn zu lesen! Ich bin mir nicht sicher, was ich davon halten soll, in einigen Fällen einen sicheren Zugriff auf Felder zu ermöglichen. Ich bin der Meinung, dass unsere Bemühungen, eine "minimale" Vorstellung davon zu machen, was unsicher ist (nur Zeiger dereferenzieren), im Nachhinein ein Fehler waren, und wir hätten einen größeren Teil der Dinge für unsicher erklären sollen (z. B. viele Abgüsse), da sie auf komplexe Weise mit LLVM interagieren. Ich denke, dass dies auch hier der Fall sein könnte. Anders ausgedrückt, ich könnte die Regeln für unsafe lieber zurückziehen, um weitere Fortschritte bei den Richtlinien für unsicheren Code zu erzielen.

@ Joshtriplett
"Trivial zerstörbare Felder" habe ich den Wortlaut angepasst.

Schlagen Sie vor, dass das gesamte unsichere Verhalten beim Lesen auftritt, wenn Sie eine Interpretation auswählen?

Ja. Das Schreiben allein kann ohne anschließendes Lesen nichts Gefährliches verursachen.

BEARBEITEN:

Ich bin der Meinung, dass unsere Bemühungen, eine "minimale" Vorstellung davon zu machen, was unsicher ist (nur Zeiger dereferenzieren), im Nachhinein ein Fehler waren

Oh.
Sichere Schreibvorgänge entsprechen voll und ganz dem aktuellen Ansatz zur Unsicherheit, aber wenn Sie ihn ändern wollen, sollte ich wahrscheinlich warten.

Ich finde es nicht gut, diese Untergruppe zu stabilisieren. Wenn wir eine Teilmenge stabilisieren, handelt es sich normalerweise um eine syntaktische Teilmenge oder zumindest eine ziemlich offensichtliche Teilmenge. Diese Untergruppe fühlt sich für mich etwas komplex an. Wenn die Funktion so unentschlossen ist, dass wir nicht bereit sind, die aktuelle Implementierung zu stabilisieren, lasse ich das Ganze lieber noch eine Weile instabil.

@nrc
Nun, die Teilmenge ist ziemlich offensichtlich - "FFI-Gewerkschaften" oder "C-Gewerkschaften" oder "Gewerkschaften vor C ++ 11" - auch wenn sie nicht syntaktisch sind. Mein ursprüngliches Ziel war es, diese Teilmenge so schnell wie möglich zu stabilisieren (idealerweise in diesem Zyklus), damit sie in Bibliotheken wie winapi .
Die verbleibende Teilmenge und ihre Implementierung sind nicht besonders zweifelhaft. Sie ist einfach nicht dringend und muss auf unklare Zeit warten, bis der Prozess für "Unions 1.2" -RFC abgeschlossen ist. Meine Erwartungen wären, die verbleibenden Teile in 1, 2 oder 3 Zyklen nach der Stabilisierung der anfänglichen Teilmenge zu stabilisieren.

Ich glaube, ich habe ein ultimatives Argument für sichere Feldzuweisungen.
Unsichere Feldzuordnung

unsafe {
    u.trivially_destructible_field = value;
}

entspricht einer sicheren vollständigen Gewerkschaftszuweisung

u = U { trivially_destructible_field: value };

mit der Ausnahme, dass die sichere Version paradoxerweise weniger sicher ist, da sie die Bytes von u außerhalb von trivially_destructible_field mit undefs überschreibt, während die Feldzuweisung garantiert, dass sie intakt bleiben.

@petrochenkov Das Extrem davon ist size_of_val(&value) == 0 , oder?

Die Äquivalenz zwischen den beiden Snippets ist nur dann wahr, wenn das betreffende Feld ist
"trivial zerstörbar", nein?

In diesem Sinne sind solche Aufgaben sicher, aber nur in einigen Fällen
scheint mir äußerst inkonsistent.

Am 22. Februar 2017 um 14:50 Uhr meldete "Vadim Petrochenkov" [email protected]
schrieb:

Ich glaube, ich habe ein ultimatives Argument für sichere Feldzuweisungen.
Unsichere Feldzuordnung

unsicher {
u.trivially_destructible_field = value;
}}

entspricht einer sicheren vollständigen Gewerkschaftszuweisung

u = U {trivial_destructible_field: value};

außer dass die sichere Version paradoxerweise weniger sicher ist, weil es so sein wird
Überschreiben Sie die Bytes von u außerhalb des trivial_destructible_field mit undefs.
Während der Feldzuweisung wird garantiert, dass sie intakt bleiben.

- -
Sie erhalten dies, weil Sie erwähnt wurden.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment-281660298 ,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/AApc0lUOXLU5xNTfM5PEfEz9nutMZhXUks5rfC8UgaJpZM4IDXsj
.

@eddyb

Das Extrem davon ist size_of_val (& value) == 0, richtig?

Ja.

@ Nagisa
Ich verstehe nicht, warum eine zusätzliche einfache Regel, die einen großen Teil der falsch positiven Ergebnisse eliminiert, äußerst inkonsistent ist. "Nur einige Fälle" betreffen insbesondere alle FFI-Gewerkschaften.
Ich denke, "extrem inkonsistent" ist eine große Überschätzung. Nicht so groß wie "Nur mut Variablen können zugewiesen werden? Schreckliche Inkonsistenz!", Aber immer noch in diese Richtung.

@petrochenkov bitte betrachten Sie einen solchen Fall:

// Somebody Somewhere in some crate (v 1.0.0)
struct Peach; // trivially destructible
union Banana { pub actually: Peach }

// Somebody Else in their dependent crate
extern some crate;
fn somefn(banana: &mut Banana) {
    banana.actually = Peach;
}

Da das Hinzufügen von Trait-Implementierungen im Allgemeinen keine bahnbrechende Änderung darstellt, stellt Mr. Somebody Somewhere fest, dass es möglicherweise eine gute Idee ist, die folgende Implementierung hinzuzufügen

impl Drop for Peach { fn drop(&mut self) { println!("Moi Peach!") }

und veröffentlichen Sie eine 1.1.0-Version (Semver kompatibel mit 1.0.0 AFAIK) der Kiste.

Plötzlich wird die Kiste von Mr. Somebody Else nicht mehr kompiliert:

fn somefn(banana: &mut Banana) {
    banana.actually = Peach; // ERROR: Something something… unsafe assingment… somewhat somewhat trivially indestructible… 
}

Und deshalb ist es manchmal nicht so trivial, sichere Zuordnungen zu Gewerkschaftsfeldern zuzulassen, da nur mut Einheimische mutationsfähig sind.


Als ich dieses Beispiel niedergeschrieben habe, bin ich mir nicht sicher, welche Haltung ich dazu einnehmen soll, ehrlich. Einerseits möchte ich die Eigenschaft beibehalten, dass das Hinzufügen von Implementierungen im Allgemeinen keine bahnbrechende Änderung darstellt (potenzielle Fälle von XID werden ignoriert). Andererseits ist das Ändern eines Bereichs der Vereinigung von trivial zerstörbar zu nicht trivial zerstörbar offensichtlich eine halb inkompatible Änderung, die extrem leicht zu übersehen ist, und die vorgeschlagene Regel würde diese Inkompatibilitäten sichtbarer machen (solange sich die Zuweisung nicht in einem unsicheren Block befindet bereits).

@ Nagisa
Dies ist ein gutes Argument, ich habe nicht über Kompatibilität nachgedacht.

Das Problem scheint jedoch lösbar zu sein. Um Kompatibilitätsprobleme zu vermeiden, machen Sie dasselbe wie Kohärenz - vermeiden Sie negative Argumente. Dh "trivial-destructible" ersetzen == "keine Komponenten implementieren Drop " durch engste positive Annäherung - "implementiert Copy ".
Copy Typ Copy abwärtskompatibel aufheben, und die Typen Copy repräsentieren immer noch die Mehrheit der "trivial zerstörbaren" Typen, insbesondere im Zusammenhang mit FFI-Gewerkschaften.

Die Implementierung von Drop ist bereits nicht abwärtskompatibel und hat nichts mit der Union-Funktion zu tun:

// Somebody Somewhere in some crate (v 1.0.0)
struct Apple; // trivially destructible
struct Pineapple { pub actually: Apple }

// Somebody Else in their dependent crate
extern some crate;
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually
}
// some crate v 1.1.0
impl Drop for Pineapple { fn drop(&mut self) { println!("Moi Pineapple!") }
fn pineapple_to_apple(pineapple: Pineapple) -> Apple {
    pineapple.actually // ERROR: can't move out of Pineapple
}

Was sich wiederum so anhört, als würde die implizite Kopie von Drop Drops implementiert. Und kopieren
kann abhängig sein.

Am Mittwoch, 22. Februar 2017, um 10:11 Uhr schrieb jethrogb [email protected] :

Die Implementierung von Drop ist bereits nicht abwärtskompatibel
nichts mit der Gewerkschaftsfunktion zu tun:

// Jemand irgendwo in einer Kiste (v 1.0.0)
Struktur Apple; // trivial zerstörbar
struct Pineapple {Pub eigentlich: Apple}

// Jemand anderes in ihrer abhängigen Kiste
extern eine Kiste;
fn pineapple_to_apple (Ananas: Ananas) -> Apple {
Ananas
}}

// eine Kiste v 1.1.0
impl Drop für Ananas {fn drop (& mut self) {println! ("Moi Pineapple!")}

fn pineapple_to_apple (Ananas: Ananas) -> Apple {
banana.actually // ERROR: Ananas kann nicht verlassen werden
}}

- -
Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworte direkt auf diese E-Mail und sieh sie dir auf GitHub an
https://github.com/rust-lang/rust/issues/32836#issuecomment-281752949 ,
oder schalten Sie den Thread stumm
https://github.com/notifications/unsubscribe-auth/ABxXhgbFgRNzYOsU4c6Gu1KFfwdjDHn3ks5rfHpYgaJpZM4IDXsj
.

@ Jethrogb
Ich wollte dieses Problem erwähnen, tat es aber nicht, weil es nur wenige besondere Voraussetzungen hat - die Struktur, die Drop implementiert, sollte ein öffentliches Feld haben und dieses Feld sollte nicht Copy . Der Vereinigungsfall betrifft alle Strukturen bedingungslos.

@petrochenkov Ich

@petrochenkov Was ist der Entwicklungs- und nächtliche Benutzererfahrungspfad zur Stabilisierung einer Teilmenge? Fügen wir zuerst ein neues Feature-Gate für die Teilmenge hinzu, damit die Benutzer konkrete Erfahrungen mit der Teilmenge sammeln können, bevor sie sich stabilisiert?

@pnkfelix
Was ich angenommen habe, ist einfach aufzuhören, #[feature(untagged_unions)] für diese Teilmenge zu benötigen, keine neuen Funktionen oder andere Bürokratie.
Gewerkschaften im FFI-Stil sollen die am häufigsten verwendete Art von Gewerkschaften sein, daher würde eine neue Funktion einen garantierten Bruch kurz vor der Stabilisierung bedeuten, was meiner Meinung nach ärgerlich wäre.

Ich möchte nur darauf hinweisen, dass ich noch keine starke Notwendigkeit habe, dies zu stabilisieren, da die Ausrichtungs- und Packungsattribute noch nicht implementiert wurden (egal stabilisiert).

@ retep998
Verpackung? Wenn Sie #[repr(packed)] meinen, wird dies derzeit von Gewerkschaften unterstützt (im Gegensatz zu align(>1) Attributen).

@petrochenkov #[repr(packed(N))] . In Winapi ist eine andere Verpackung als 1 erforderlich. Es ist nicht so, dass ich diese Dinge speziell von Gewerkschaften unterstützen muss. Ich möchte einfach nicht zu einer neuen Hauptversion springen, um meine Mindestanforderungen an Rust zu erhöhen, es sei denn, ich kann all diese Dinge gleichzeitig erhalten.

Um den Stand der Dinge hier etwas zu verdeutlichen:

Der aktuelle FCP-Vorschlag gilt nur für reine Copy Gewerkschaften. Dies ist meines Wissens der Fall bei grundsätzlich keinen offenen Fragen außer "Sollten wir uns stabilisieren?". Die Diskussion seit dem Antrag an FCP drehte sich alles um den neuen RFC von

@nrc und @nikomatsakis , aus der Diskussion im IRC, ich vermute, dass Sie beide bereit sind, Ihre Kästchen

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

@petrochenkov
Es scheint mir, dass dies von einer Idee profitieren würde, die ich kürzlich auf den Markt gebracht habe. (https://internals.rust-lang.org/t/automatic-marker-trait-for-unconditionally-valid-repr-c-types/5054)

Obwohl dies nicht für Gewerkschaften vorgeschlagen wurde, würde das Merkmal Plain wie erläutert (vorbehaltlich des Bikeshedding), die Verwendung einer Gewerkschaft, die nur aus Plain -Typen besteht, ohne Unsicherheit verwenden. Es codiert die Eigenschaft, dass jedes Bitmuster im Speicher gleichermaßen gültig ist, sodass Sie die Initialisierungsprobleme lösen können, indem Sie festlegen, dass der Speicher auf Null gesetzt wird, und es stellt sicher, dass Lesevorgänge UB nicht aufrufen können.

Im Zusammenhang mit FFI ist die Definition von Plain in vielen Fällen auch de facto eine Voraussetzung dafür, dass ein solcher Code einwandfrei funktioniert, während Fälle, in denen Nicht- Plain -Typen nützlich sind, selten und schwer einzurichten sind sicher.

Abgesehen von der tatsächlichen Existenz eines benannten Merkmals kann es ratsam sein, dieses Merkmal in zwei Teile zu teilen. Mit unqualifizierten union , die die Anforderungen durchsetzen und die Verwendung ohne Unsicherheit ermöglichen, und unsafe union mit entspannten Anforderungen an den Inhalt und mehr Kopfschmerzen für Benutzer. Dies würde eine Stabilisierung ermöglichen, ohne den Weg für die Hinzufügung des anderen in der Zukunft zu versperren.

@ le-jzr
Dies scheint für die Gewerkschaften ausreichend orthogonal zu sein.
Ich würde schätzen, dass es in naher Zukunft nicht sehr wahrscheinlich ist, Plain in Rust zu akzeptieren. Mehr Zugriffe auf Gewerkschaftsfelder sicher zu machen, ist mehr oder weniger abwärtskompatibel (nicht ausschließlich aufgrund von Lints), sodass ich die fälligen Gewerkschaften nicht verzögern würde dazu.

@petrochenkov Ich

Daher mein Vorschlag, die Deklaration unsafe union abzugeben, damit die bedingungslos sicher zu verwendende Version später eingeführt werden kann, ohne neue Schlüsselwörter hinzuzufügen. Es ist erwähnenswert, dass für die meisten Anwendungsfälle eine vollständig sicher zu verwendende Version ausreicht.

Bearbeiten: Versucht zu klären, was ich sagen will.

Ein weiterer Ort, an dem diese Möglichkeit das aktuelle und zukünftige Design beeinflussen kann, ist die Initialisierung. Für eine bedingungslos sichere Vereinigung ist es erforderlich, dass die gesamte dafür reservierte Speicherspanne auf Null gesetzt wird. Selbst mit der aktuellen Version würde dies sicherstellen, dass potenzielle UB-Szenarien reduziert und die Verwendung von Gewerkschaften vereinfacht werden.

Die letzte Kommentierungsfrist ist nun abgeschlossen.

Was ist der nächste Schritt hier, nachdem der zusammenzuführende FCP abgeschlossen ist? Wäre schön dies für 1.19 zu stabilisieren.

Es wäre fantastisch, wenn jemand dieser @ rfcbot- Nachricht ( Quellcode hier ) mehr Details hinzufügen könnte. Es könnte für jemanden, der mit dem Prozess nicht vertraut ist, einfacher sein, einzuspringen und Dinge voranzutreiben.

Der Weg ist frei, um dies in 1.19 zu bringen. Jemand am Haken dafür? cc @joshtriplett

Wird die Layoutoptimierung für NonZero enum garantiert über ein union angewendet? Zum Beispiel sollte Option<ManuallyDrop<&u32>> nicht None als Nullzeiger darstellen. Some(ManuallyDrop::new(uninitialized::<[Vec<Foo>; 10]>())).is_some() sollte keinen nicht initialisierten Speicher lesen.

https://crates.io/crates/nodrop (verwendet in https://crates.io/crates/arrayvec) hat Hacks, um damit umzugehen.

@ SimonSapin
Dies ist derzeit im RFC als ungelöste Frage markiert.
In der aktuellen Implementierung dieses Programms

#![feature(untagged_unions)]

struct S {
    _a: &'static u8
}
union U {
    _a: &'static u8
}

fn main() {
    use std::mem::size_of;
    println!("struct {}", size_of::<S>());
    println!("optional struct {}", size_of::<Option<S>>());
    println!("union {}", size_of::<U>());
    println!("optional union {}", size_of::<Option<U>>());
}

druckt

struct 8
optional struct 8
union 8
optional union 16

dh die Optimierung wird nicht durchgeführt.
cc https://github.com/rust-lang/rust/issues/36394

Es ist unwahrscheinlich, dass dies 1,19 ergibt.

@ Brson

Es ist unwahrscheinlich, dass dies 1,19 ergibt.

Die Stabilisierungs-PR wird zusammengeführt.

Da Gewerkschaften ohne Tags jetzt in Version 1.19 ausgeliefert werden (teilweise von https://github.com/rust-lang/rust/pull/42068) - gibt es noch etwas zu diesem Thema oder sollten wir schließen?

@ Jonathandturner
Es gibt immer noch eine ganze Welt von Gewerkschaften mit Nicht- Copy Feldern!
Der Fortschritt ist im RFC zur Klärung / Dokumentation (https://github.com/rust-lang/rfcs/pull/1897) größtenteils blockiert.

Hat es seit August Fortschritte bei Gewerkschaften mit Nicht- Copy -Feldern gegeben? Der RFC der Gewerkschaften 1.2 scheint ins Stocken geraten zu sein (ich vermute aufgrund der Implikationsperiode?)

Das Zulassen von ?Sized -Typen in Gewerkschaften - wenn auch nur für Gewerkschaften eines einzelnen Typs - würde die Implementierung von https://github.com/rust-lang/rust/issues/47034 vereinfachen:

`` `Rost
Vereinigung ManuallyDrop{
Wert: T.
}}

@mikeyhew Du

Ich betrachte einen Rust-Code mit union s und habe keine Ahnung, ob er undefiniertes Verhalten hervorruft oder nicht.

In der Referenz [items :: unions] wird nur Folgendes erwähnt:

Auf inaktive Felder kann ebenfalls zugegriffen werden (mit derselben Syntax), wenn sie ausreichend mit dem aktuellen Wert kompatibel sind, der von der Union beibehalten wird. Das Lesen inkompatibler Felder führt zu undefiniertem Verhalten.

Aber ich kann weder in [items :: unions] noch in [type_system :: type_layout] eine Definition von "

Beim Durchsehen der RFCs konnte ich auch keine Definition von "Layout-kompatibel" finden, sondern nur handgewellte Beispiele dafür, was im RFC 1897: Unions 1.2 (nicht zusammengeführt) funktionieren sollte und was nicht.

Die RFC1444: Gewerkschaften scheinen nur die Umwandlung einer Gewerkschaft in ihre Varianten zuzulassen, solange sie kein undefiniertes Verhalten

Sind die _präzisen_ Regeln, die mir sagen, ob ein Code, der Gewerkschaften verwendet, irgendwo definiertes Verhalten aufgeschrieben hat (und wie das definierte Verhalten ist)?

@gnzlbg In erster Näherung: Sie greifen möglicherweise nicht auf Padding zu, greifen möglicherweise nicht auf eine Aufzählung zu, die eine ungültige Diskriminante enthält, greifen möglicherweise nicht auf einen Bool zu, der einen anderen Wert als true oder false enthält, greifen möglicherweise nicht auf einen ungültigen oder signalisierenden Gleitkommawert zu und ein paar andere Dinge wie diese.

Wenn Sie auf einen bestimmten Code verweisen, an dem Gewerkschaften beteiligt sind, können wir ihn uns ansehen und Ihnen mitteilen, ob er etwas Undefiniertes tut.

In erster Näherung: Sie greifen möglicherweise nicht auf das Auffüllen zu, greifen möglicherweise nicht auf eine Aufzählung zu, die eine ungültige Diskriminante enthält, greifen möglicherweise nicht auf einen Bool zu, der einen anderen Wert als true oder false enthält, greifen möglicherweise nicht auf einen ungültigen oder signalisierenden Gleitkommawert zu, und ein paar andere Dinge wie diese.

Tatsächlich ist der jüngste Konsens, dass das Lesen beliebiger Floats in Ordnung ist (# 46012).

Ich würde noch eine weitere Anforderung hinzufügen: Sowohl die Quell- als auch die Ziel-Union-Variante sind #[repr(C)] ebenso wie alle ihre Felder (und rekursiv), wenn es sich um Strukturen handelt.

@Amanieu Ich stehe korrigiert, danke.

Also denke ich, dass die Regeln nirgendwo geschrieben sind?

Ich schaue mir an, wie man stdsimd mit seiner neuen Oberfläche verwendet. Wenn wir nicht einige Extras damit stabilisieren, müssen Gewerkschaften verwendet werden, um mit einigen der simd-Typen Typ-Punning durchzuführen, wie folgt:

https://github.com/rust-lang-nursery/stdsimd/blob/03cb92ddce074a5170ed5e5c5c20e5fa4e4846c3/coresimd/src/x86/test.rs#L17

AFAIK, das ein Gewerkschaftsfeld schreibt und ein anderes liest, ähnelt der Verwendung von transmute_copy mit denselben Einschränkungen. Dass diese Einschränkungen immer noch ein bisschen nebulös sind, ist nicht gewerkschaftsspezifisch.

Für diese Angelegenheit könnte die von Ihnen verknüpfte Funktion nur transmute::<__m128d, [f64; 2]> . Obwohl die Union-Version wohl besser ist, wird mindestens einmal die derzeit vorhandene Transmute entfernt: Es könnte nur A { a }.b[idx] .

@rkruppe Ich habe eine Clippy-Ausgabe ausgefüllt, um diese Flusen hinzuzufügen: https://github.com/rust-lang-nursery/rust-clippy/issues/2361

Die von Ihnen verknüpfte Funktion könnte einfach transmute :: <__ m128d i = "8"> verwenden

Ich denke, die Regeln, nach denen ich suche, sind, wann Transmute dann undefiniertes Verhalten hervorruft (also werde ich nach diesen suchen).

Ich denke, es hätte mir geholfen, wenn die Sprachreferenz über Gewerkschaften die Regeln für Gewerkschaften in Bezug auf die Umwandlung festgelegt hätte (auch wenn die Regeln für die Umwandlung noch nicht 100% klar sind), anstatt nur "Layoutkompatibilität" zu erwähnen und sie zu belassen dabei. Der Sprung von "Layoutkompatibilität" zu "Wenn Transmute kein undefiniertes Verhalten hervorruft, sind die Typen layoutkompatibel und können über Typ Punning aufgerufen werden" war mir nicht klar.

Um klar zu sein, ist transmute [_copy] nicht "primitiver" als Gewerkschaften. Tatsächlich ist transmute_copy buchstäblich nur ein Zeiger auf as Casts plus ptr::read . transmute benötigt zusätzlich mem::uninitialized (veraltet) oder MaybeUninitialized (eine Gewerkschaft) oder ähnliches und wird als intrinsisch für die Effizienz implementiert, aber es läuft auch auf einen Typ hinaus. punning memcpy. Der Hauptgrund, warum ich die Verbindung zur Umwandlung hergestellt habe, ist, dass sie älter und historisch überbetont ist und wir daher derzeit mehr Artikel und Folklorewissen haben, die sich speziell auf die Umwandlung konzentrieren. Das eigentliche zugrunde liegende Konzept, das vorschreibt, was gültig ist und was nicht (und was eine Spezifikation beschreiben würde), besteht darin, wie Werte als Bytes im Speicher gespeichert werden und welche Byte-Sequenzen UB sind, um als welche Typen gelesen zu werden.

Korrektur: Transmute benötigt keinen nicht initialisierten Speicher (über Intrinsics, Gewerkschaften oder auf andere Weise). Abgesehen von der Effizienz können Sie so etwas tun (ungetestet, kann peinliche Tippfehler enthalten):

fn transmute<T, U>(x: T) -> U {
    assert!(size_of::<T>() == size_of::<U>());
    let mut bytes = [0u8; size_of::<U>()];
    ptr::write(bytes.as_mut_ptr() as *mut T, x);
    mem::forget(x);
    ptr::read(bytes.as_ptr() as *const U)
}

Der einzige "magische" Teil der Umwandlung besteht darin, dass die Typparameter beim Kompilieren auf die gleiche Größe beschränkt werden

Die Referenz und und Unions 1.2 RFC sind in dieser Angelegenheit absichtlich vage, da die Regeln für die Umwandlung im Allgemeinen nicht festgelegt sind.
Die Absicht war "für repr(C) Gewerkschaften siehe ABI-Spezifikationen von Drittanbietern, für repr(Rust) Gewerkschaften ist die Layoutkompatibilität größtenteils nicht spezifiziert (sofern dies nicht der Fall ist)".

Ist es zu spät, die Drop-Check-Semantik von Gewerkschaften mit Drop-Feldern zu überdenken?

Das ursprüngliche Problem ist, dass das Hinzufügen von ManuallyDrop dass Josephine nicht mehr gesund war, da es (ziemlich ungezogen) auf dauerhaft geliehenen Werten beruhte, deren Backing Store nicht zurückgefordert wurde, ohne zuvor ihren Destruktor ausgeführt zu haben.

Ein reduziertes Beispiel finden Sie unter https://play.rust-lang.org/?gist=607e2dfbd51f4062b9dc93d149815695&version=nightly. Die Idee ist, dass es einen Typ Pin<'a, T> , mit einer Methode pin(&'a self) -> &'a T deren Sicherheit von der Invariante abhängt "nach dem Aufruf von pin.pin() , wenn der Speicher, der den Pin unterstützt, jemals zurückgefordert wird Der Destruktor des Pins muss ausgeführt worden sein. "

Diese Invariante wurde von Rust beibehalten, bis #[allow(unions_with_drop_fields)] hinzugefügt und von ManuallyDrop https://doc.rust-lang.org/src/core/mem.rs.html#949 verwendet wurde.

Die Invariante würde wiederhergestellt, wenn der Drop-Checker Gewerkschaften mit Drop-Feldern als Drop-Impl betrachten würde. Dies ist eine bahnbrechende Änderung, aber ich bezweifle, dass sich Code in freier Wildbahn auf die aktuelle Semantik stützt.

IRC-Konversation: https://botbot.me/mozilla/rust-lang/2018-02-01/?msg=96386869&page=3

Josephine-Problem: https://github.com/asajeffrey/josephine/issues/52

cc: @nox @eddyb @pnkfelix

Das ursprüngliche Problem ist, dass das Hinzufügen von ManuallyDrop dazu führte, dass Josephine nicht mehr gesund war, da es (ziemlich ungezogen) auf dauerhaft geliehenen Werten beruhte, deren Backing Store nicht zurückgefordert wurde, ohne zuvor ihren Destruktor ausgeführt zu haben.

Es wird nicht garantiert, dass Destruktoren ausgeführt werden. Rust garantiert das nicht. Es wird versucht, aber zum Beispiel wurde std::mem::forget zu einer sicheren Funktion gemacht.

Die Invariante würde wiederhergestellt, wenn der Drop-Checker Gewerkschaften mit Drop-Feldern als Drop-Impl betrachten würde. Dies ist eine bahnbrechende Änderung, aber ich bezweifle, dass sich Code in freier Wildbahn auf die aktuelle Semantik stützt.

Gewerkschaften sind größtenteils unsicher, weil Sie nicht wissen können, welcher Bereich der Gewerkschaft gültig ist. Die Gewerkschaft kann kein automatisches Drop impl haben; Wenn Sie ein solches Impl haben möchten, müssen Sie es manuell schreiben und dabei berücksichtigen, was auch immer bedeutet, dass Sie wissen müssen, ob das Vereinigungsfeld mit einem Drop Impl gültig ist.

Eine Klarstellung hier: Ich glaube nicht, dass wir Gewerkschaften mit Drop -Feldern standardmäßig zulassen sollten , ohne mindestens eine Warn-by-Default-Flusen, wenn nicht eine Fehler-Standard-Flusen. unions_with_drop_fields sollte im Rahmen des Stabilisierungsprozesses nicht verschwinden.

EDIT: Ups, wollte nicht "Schließen und Kommentieren" drücken.

@joshtriplett Ja, Rust garantiert nicht, dass Destruktoren ausgeführt werden, aber es ist (vor 1.19) passiert, dass die Invariante beibehalten wurde, dass bei dauerhaft geliehenen Werten der Speicher nur dann wiederhergestellt wird, wenn der Destruktor ausgeführt wird. Dies gilt sogar in Gegenwart von mem::forget , da Sie es nicht für einen dauerhaft geliehenen Wert aufrufen können.

Dies ist es, worauf sich Joephine ziemlich frech verlassen hat, aber es ist nicht mehr wahr, weil der Drop Checker unions_with_drop_fields .

Es wäre in Ordnung, wenn allow(unions_with_drop_fields) als unsichere Anmerkung angesehen würde. Dies wäre keine drastische Änderung, AFAICT. Es würde nur deny(unsafe_code) erfordern, um nach allow(unions_with_drop_fields) suchen.

@asajeffrey Ich versuche immer noch, das Pin Ding zu verstehen ... Wenn ich also dem Beispiel richtig folge, ist der Grund, warum dies "funktioniert", dass fn pin(&'a Pin<'a, T>) -> &'a T die Ausleihe zwingt, als zu halten solange die Lebensdauer 'a im Typ

Das ist eine interessante Beobachtung! Ich war mir dieses Tricks nicht bewusst. Mein Bauchgefühl ist, dass dies "aus Versehen" funktioniert, dh, dass sicherer Rost keine Möglichkeit bietet, den Destruktor am Laufen zu hindern, aber das macht diesen Teil des "Vertrags" nicht aus. Insbesondere listet https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html keine Lecks auf.

IMO ist es egal, ob es versehentlich oder absichtlich funktioniert. Es gab keine Möglichkeit zu vermeiden, dass Drop mit diesem Trick ausgeführt wurde, bevor ManuallyDrop vorhanden war (was die Implementierung von unsicherem Code erfordert), und jetzt können wir uns nicht mehr darauf verlassen.

Die Hinzufügung von ManuallyDrop hat dieses nette Verhalten von Rust im Grunde genommen getötet und zu sagen, dass man sich überhaupt nicht darauf verlassen sollte, klingt für mich nach Zirkelschluss. Wenn ManuallyDrop es nicht erlaubt hätte, Pin::pin , gäbe es eine andere Möglichkeit, das Anrufen von Pin::pin unsound zu machen? Das glaube ich nicht.

Ich glaube nicht, dass wir uns dazu verpflichten können, jede Garantie zu bewahren, die rustc gerade versehentlich bietet. Wir haben keine Ahnung, wie diese Garantien aussehen könnten, also würden wir ein Schwein in einem Sack stabilisieren (okay, ich hoffe, diese Redewendung macht Sinn ... es ist das, was mir das Wörterbuch sagt, das mit meiner Muttersprachensprache übereinstimmt, was wörtlich übersetzt " die Katze in der Tasche ";) - was ich sagen möchte ist, wir hätten keine Ahnung, was wir stabilisieren würden).

Dies ist auch ein zweischneidiges Schwert - jede zusätzliche Garantie, die wir anbieten, ist etwas, um das sich unsicherer Code kümmern muss. Kann genausogut brechen bestehenden unsicheren Code so eine neue Garantie zu entdecken (leise irgendwo auf crates.io sitzen ohne sich dessen bewusst zu sein) als neue unsicheren Code ermöglichen (letzteres war hier der Fall ist).

Zum Beispiel ist es sehr denkbar, dass lexikalische Lebensdauern unsicheren Code ermöglichen, der durch nicht lexikalische Lebensdauern unterbrochen wird. Derzeit sind alle Lebensdauern gut verschachtelt. Gibt es möglicherweise eine Möglichkeit für unsicheren Code, dies auszunutzen? Nur bei nicht-lexikalischen Lebensdauern kann es Lebensdauern geben, die sich überlappen, aber keine ist in der anderen enthalten. Macht dies NLL zu einer bahnbrechenden Veränderung? Ich hoffe nicht!

Wenn ManuallyDrop das Aufrufen von Pin :: Pin nicht zulässt, gibt es dann eine andere Möglichkeit, das Aufrufen von Pin :: Pin nicht mehr sinnvoll zu machen? Das glaube ich nicht.

Mit unsafe Code würde es geben. Wenn Sie also diesen Pin Trick-Sound deklarieren, deklarieren Sie einen unsicheren Code unsound , der Sound wäre , wenn wir entscheiden, dass ManuallyDrop in Ordnung ist.

Was wir beschrieben haben, ist eine sehr ergonomische Möglichkeit, Rust in GCs zu integrieren. Ich versuche zu sagen, dass es für mich falsch klingt, uns zu sagen, dass dies nur ein Unfall war, dass es funktioniert hat und dass wir es vergessen sollten, wenn ich keinen Anwendungsfall finde, um Gewerkschaften nicht mit Drop Felder wie von @asajeffrey hier beschrieben, und wenn dies wirklich die einzige Warze ist, die Josephine bricht.

Ich werde es gerne vergessen, wenn jemand zeigen kann, dass es auch ohne ManuallyDrop .

Ich versuche zu sagen, dass es für mich falsch klingt, uns zu sagen, dass dies nur ein Unfall war, bei dem es funktioniert hat

Ich sehe keinen Hinweis darauf, dass dieser Trick jemals "entworfen" wurde, daher halte ich es für fair, ihn als Unfall zu bezeichnen.

und dass wir es vergessen sollten

Ich hätte klarer machen sollen, dass dieser Teil nur mein persönliches Bauchgefühl ist. Ich denke, es könnte auch ein vernünftiger Punkt sein, dies als "glücklichen Unfall" zu deklarieren und es tatsächlich zu einer Garantie zu machen - wenn wir ziemlich sicher sind, dass tatsächlich alle anderen unsafe -Codes diese Garantie respektieren, und das Die Bereitstellung dieser Garantie ist wichtiger als der Anwendungsfall ManuallyDrop . Dies ist ein Kompromiss, ähnlich wie bei der Leakpocalypse, bei dem wir unseren Kuchen nicht essen können und ihn auch haben (wir können nicht beide Rc mit der aktuellen API und die drop - haben). Threads mit Gültigkeitsbereich, wir können nicht sowohl ManuallyDrop als auch Pin haben, also müssen wir eine Entscheidung treffen, wie auch immer.

Trotzdem fällt es mir schwer, die hier gewährte tatsächliche Garantie präzise auszudrücken, was mich persönlich dazu veranlasst, mich mehr auf die Seite " ManuallyDrop ist in Ordnung" zu konzentrieren.

wenn wir ziemlich sicher sind, dass tatsächlich alle anderen unsafe -Codes diese Garantie respektieren und dass die Bereitstellung dieser Garantie wichtiger ist als der Anwendungsfall ManuallyDrop . Dies ist ein Kompromiss, ähnlich wie bei der Leakpocalypse, bei dem wir unseren Kuchen nicht essen können und ihn auch haben (wir können nicht beide Rc mit der aktuellen API und die drop - haben). Threads mit Gültigkeitsbereich, wir können nicht sowohl ManuallyDrop als auch Pin haben, also müssen wir eine Entscheidung treffen, wie auch immer.

Fair genug, dem stimme ich von Herzen zu. Beachten Sie, dass, wenn wir das betrachten, was @asajeffrey am Ende als undefiniertes Verhalten beschrieben hat, dies eine auf drop basierende Thread-API mit Gültigkeitsbereich zurückbringen kann.

Soweit ich weiß, besteht Alans Vorschlag nicht darin, ManuallyDrop zu entfernen, sondern Dropck davon auszugehen, dass es (und andere Gewerkschaften mit Drop -Feldern) einen Destruktor hat. (Dass Destruktoren zufällig nichts tun, aber ihre bloße Existenz beeinflusst, welche Programme Dropck akzeptiert oder ablehnt.)

Ich werde es gerne vergessen, wenn jemand zeigen kann, dass es auch ohne ManuallyDrop nicht gesund war.

Ich bin mir nicht sicher, ob dies qualifiziert ist, aber hier ist mein erster Versuch: Eine alberne Implementierung von so etwas wie ManuallyDrop , die in Rust vor union funktioniert.

pub mod manually_drop {
    use std::mem;
    use std::ptr;
    use std::marker::PhantomData;

    pub struct ManuallyDrop<T> {
        data: [u8; 32],
        phantom: PhantomData<T>,
    }

    impl<T> ManuallyDrop<T> {
        pub fn new(x: T) -> ManuallyDrop<T> {
            assert!(mem::size_of::<T>() <= 32);
            let mut data = [0u8; 32];
            unsafe {
                ptr::copy(&x as *const _ as *const u8, &mut data[0] as *mut _, mem::size_of::<T>());
            }
            mem::forget(x);
            ManuallyDrop { data, phantom: PhantomData }
        }

        pub fn deref(&self) -> &T {
            unsafe {
                &*(&self.data as *const _ as *const T)
            }
        }
    }
}

(Ja, ich muss wahrscheinlich noch etwas arbeiten, um die Ausrichtung richtig zu machen, aber das könnte auch durch Opferung einiger Bytes geschehen.)
Der Spielplatz, auf dem dies angezeigt wird, bricht Pin : https://play.rust-lang.org/?gist=fe1d841cedb13d45add032b4aae6321e&version=nightly

Das habe ich oben mit zweischneidigem Schwert gemeint - soweit ich sehen kann, respektiert mein ManuallyDrop alle Regeln, die wir aufgestellt haben. Wir haben also zwei Teile inkompatiblen unsicheren Codes - ManuallyDrop und Pin . Wer ist "richtig"? Ich würde sagen, Pin beruht auf Garantien, die wir nie gegeben haben, und daher ist es hier "falsch", aber dies ist ein Urteilsspruch, kein Beweis.

Das ist interessant. In einigen Versionen unseres Pinning-Materials nimmt Pin::pin ein &'this mut Pin<'this, T> , aber es wäre nicht unangemessen, wenn Ihr ManuallyDrop ein DerefMut Impl hat, richtig ?

Hier ist ein Spielplatz, der zeigt, dass @RalfJungs (nicht überraschend) immer noch Pin mit einer &mut -taking pin -Methode bricht.

https://play.rust-lang.org/?gist=5057570b54952e245fa463f8d7719663&version=nightly

Es wäre nicht unangemessen für Ihren ManuallyDrop, ein DerefMut-Gerät zu haben, oder?

Ja, ich habe gerade die API hinzugefügt, die ich für dieses Beispiel benötigt habe. Das offensichtliche deref_mut sollte gut funktionieren.

Soweit ich weiß, schlägt Alan vor, ManuallyDrop nicht zu entfernen, sondern Dropck davon auszugehen, dass es (und andere Gewerkschaften mit Drop-Feldern) einen Destruktor hat. (Dass Destruktoren zufällig nichts tun, aber ihre bloße Existenz beeinflusst, welche Programme Dropck akzeptiert oder ablehnt.)

Ah, das hatte ich verpasst; Das tut mir leid. Wenn Sie meinem Beispiel Folgendes hinzufügen, funktioniert es trotzdem:

    unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> {
        fn drop(&mut self) {}
    }

Nur wenn ich das #[may_dangle] Rust entferne, lehnt es ab. Zumindest müssten wir uns also eine Regel ausdenken, gegen die der obige Code verstößt. Nur zu sagen, dass es einen Code gibt, mit dem wir solide sein wollen, mit dem dies nicht kompatibel ist, ist ein schlechter Aufruf, weil er es macht Es ist so gut wie unmöglich, sich einen Code anzusehen und zu überprüfen, ob er einwandfrei ist.


Ich denke, was mich an dieser "versehentlichen Garantie" am meisten beunruhigt, ist, dass ich keinen einzigen guten Grund dafür sehe, dass dies funktioniert. Die Art und Weise, wie die Dinge in Rust verkabelt sind, hält dies zusammen, aber Dropck wurde hinzugefügt, um keine Lecks zu verhindern, sondern um fehlerhafte Verweise auf tote Daten zu vermeiden (ein häufiges Problem bei Destruktoren). Die Begründung für das Funktionieren von Pin basiert nicht auf "hier ist ein Mechanismus im Rust-Compiler oder eine Systemgarantie, die ziemlich klar besagt, dass dauerhaft geliehene Daten nicht durchgesickert werden können" - sie basiert eher auf "Wir haben uns sehr bemüht und konnten keine dauerhaft geliehenen Daten verlieren. Wir denken also, dass das in Ordnung ist." Wenn ich mich auf diese Tatsache verlasse, bin ich ziemlich nervös. EDIT: Die Tatsache, dass Dropck involviert ist, macht mich noch nervöser, weil dieser Teil des Compilers eine Geschichte böser Soliditätsfehler hat. Der Grund, warum dies funktioniert, scheint zu sein, dass Dauerausleihen im Widerspruch zu sicheren drop . Dies scheint wirklich "eine Argumentation zu sein, die auf einer umfassenden Fallanalyse basiert, was man mit dauerhaft geliehenen Daten tun kann".

Um fair zu sein, könnte man ähnliche Dinge über die innere Veränderlichkeit sagen - es ist zufällig so, dass das Zulassen von Änderungen durch gemeinsame Referenzen in einigen Fällen tatsächlich sicher funktioniert, wenn wir die richtige API auswählen. Diese Arbeit jedoch macht tatsächlich benötigte explizite Unterstützung in den Compiler ( UnsafeCell ) , weil es mit Optimierungen kollidiert, und es gibt unsicheren Code, den Ton ohne innere Wandelbarkeit sein würde, aber nicht mit Innen Veränderlichkeit klingen. Ein weiterer Unterschied besteht darin, dass die Veränderlichkeit des Innenraums von Anfang an (oder von Anfang an - dies ist weit vor meiner Zeit in der Rust-Community) ein Designziel war, was bei "perma-geliehen nicht nicht durchgesickert" nicht der Fall ist. Und schließlich, für die innere Veränderlichkeit, gibt es meiner Meinung nach eine ziemlich gute Geschichte über "Teilen macht Mutation gefährlich , aber nicht unmöglich , und die API von geteilten Referenzen besagt nur, dass Sie im Allgemeinen keine Veränderlichkeit erhalten, schließt jedoch nicht aus, mehr Operationen für bestimmte zuzulassen Typen ", was zu einem kohärenten Gesamtbild führt. Natürlich habe ich viel Zeit damit verbracht, über gemeinsame Referenzen nachzudenken. Vielleicht gibt es ein ebenso kohärentes Bild für das vorliegende Problem, das mir einfach nicht bewusst ist.

Zeitzonen machen Spaß, ich bin gerade erst aufgestanden! Hier scheint es zwei Probleme zu geben (Invarianten im Allgemeinen und Dropck im Besonderen), daher werde ich sie in separate Kommentare einfügen ...

@RalfJung : Ja, dies ist ein Problem mit den Invarianten, die von unsicherem Rust aufrechterhalten werden. Für jede Version von Rust + std gibt es mehr als eine Auswahl von invarianten I die unter Verwendung von Vertrauensgarantien beibehalten werden. Und tatsächlich kann es zwei Bibliotheken geben, L1 und L2 , die inkompatible I1 und I2 , so dass Rust + L1 sicher und sicher ist Rust + L2 ist sicher, aber Rust + L1 + L2 ist unsicher.

In diesem Fall ist L1 ManuallyDrop und L2 ist Josephine , und es ist ziemlich klar, dass ManuallyDrop gewinnen wird, seit es jetzt ist in std , das viel stärkere Abwärtskompatibilitätsbeschränkungen aufweist als Josephine.

Interessanterweise lauten die Richtlinien unter https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html wie folgt: "Es liegt in der Verantwortung des Programmierers, beim Schreiben von unsicherem Code keinen sicheren Code zuzulassen zeigen diese Verhaltensweisen: ... "das heißt, es ist eine kontextbezogene Eigenschaft (für alle sicheren Kontexte kann C, C [P] nicht schief gehen) und hängt daher von der Version ab (da v1.20 von Rust + std sicherer ist Kontexte als v1.18). Insbesondere würde ich behaupten, dass das Pinning diese Einschränkung für Rust tatsächlich vor 1.20 erfüllt hat, da es keinen sicheren Kontext gab. C st C [Pinning] geht schief.

Dies ist jedoch nur eine Anwaltschaft in einer Kaserne. Ich denke, alle sind sich einig, dass es ein Problem mit dieser kontextbezogenen Definition gibt, daher alle Diskussionen über unsichere Code-Richtlinien.

Wenn nichts anderes, denke ich, dass das Feststecken ein interessantes Beispiel dafür gezeigt hat, dass zufällige Invarianten schief gehen.

Das Besondere, was Gewerkschaften ohne Tags (und damit ManuallyDrop ) taten, war die Interaktion mit dem Drop-Checker. Insbesondere ManualDrop verhält sich wie sein Defn:

unsafe impl<#[may_dangle] T> Drop for ManuallyDrop<T> { ... }

und dann können Sie ein Gespräch darüber führen, ob dies zulässig ist oder nicht :) Tatsächlich findet dieses Gespräch im Thread may_dangle , der unter https://github.com/rust-lang/rust/issues/ beginnt.

@RalfJung Ihr Code zeigt einen interessanten data T , der Typ für die Kompilierungszeit jedoch [u8; N] . Welcher Typ zählt für may_dangle ?

Interessanterweise lauten die Richtlinien unter https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html wie folgt: "Es liegt in der Verantwortung des Programmierers, beim Schreiben von unsicherem Code keinen sicheren Code zuzulassen zeigen diese Verhaltensweisen: ... "das heißt, es ist eine kontextbezogene Eigenschaft

Ah, interessant. Ich bin damit einverstanden, dass dies eindeutig nicht ausreicht - dies würde die ursprünglichen Threads mit Gültigkeitsbereich klingen lassen. Um sinnvoll zu sein, muss dies (zumindest) den Satz unsicheren Codes angeben, den der sichere Code aufrufen darf.

Persönlich denke ich, dass eine bessere Möglichkeit, dies zu spezifizieren, darin besteht, die Invarianten anzugeben, die beibehalten werden sollen. Aber ich bin hier eindeutig voreingenommen, weil die Methode, mit der ich Dinge über Rust beweise, eine solche Invariante erfordert. ;)

Ich bin ein wenig überrascht, dass die Seite keinen Haftungsausschluss enthält, vorläufig zu sein. Wir sind uns noch nicht sicher, wie genau das Limit sein wird - wie diese Diskussion zeigt. Wir benötigen unsicheren Code, um zumindest das zu tun, was in diesem Dokument steht, aber wir müssen wahrscheinlich mehr benötigen.

Zum Beispiel sind die Grenzen des undefinierten Verhaltens und die Möglichkeiten von unsicherem Code nicht dieselben. Unter https://github.com/nikomatsakis/rust-memory-model/issues/44 finden Sie eine aktuelle Diskussion zu diesem Thema: Das Duplizieren eines &mut T für mem::size_of::<T>() == 0 führt zu keinem undefinierten Verhalten direkt und wird dennoch eindeutig als illegal für unsicheren Code angesehen. Der Grund dafür ist, dass andere unsichere Codes möglicherweise davon abhängen, dass ihre Eigentumsdisziplin eingehalten wird, und dass das Duplizieren von Dingen gegen diese Disziplin verstößt.

Wenn nichts anderes, denke ich, dass das Feststecken ein interessantes Beispiel dafür gezeigt hat, dass zufällige Invarianten schief gehen.

Oh, das ist sicher. Und ich frage mich, was wir tun können, um dies in Zukunft zu vermeiden? Setzen Sie vielleicht eine große Warnung auf https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html und sagen Sie: "Nur weil eine Invariante in rustc + libstd enthalten ist, heißt das nicht, dass unsicherer Code dies kann." Verlassen Sie sich darauf, stattdessen sind hier einige Invarianten, auf die Sie sich verlassen können "?

@RalfJung Ja, ich glaube nicht, dass jemand in die kontextbezogene Definition von "Korrektheit" verliebt ist, vor allem, weil sie aufgrund der Beobachtungsgabe von Kontexten spröde ist. Ich wäre viel glücklicher mit einer semantischen Definition in Bezug auf Invarianten.

Das Einzige, worum ich bitten würde, ist, dass wir uns etwas Spielraum geben und zwei Invarianten für die Argumentation der Vertrauensgarantie definieren können (Code kann sich auf R verlassen und sollte G garantieren, wobei G R impliziert). Auf diese Weise gibt es etwas Raum, um R zu stärken und G zu schwächen. Wenn wir nur eine Invariante haben (dh R = G), können wir sie nie ändern!

Bei der ständigen Überprüfung werden derzeit keine Sonderfall-Vereinigungsfelder verwendet: (cc @solson @ oli-obk)

union Transmute<T, U> { from: T, to: U }

const SILLY: () = unsafe {
    (Transmute::<usize, Box<String>> { from: 1 }.to, ()).1
};

fn main() {
    SILLY
}

Der obige Code erzeugt den Miri-Bewertungsfehler "Aufruf von Nicht-Konstanten fn std::ptr::drop_in_place::<(std::boxed::Box<std::string::String>, ())> - shim(Some((std::boxed::Box<std::string::String>, ()))) ".

Ändern Sie es, um zu erzwingen, dass der Typ von .to vom const-checker beobachtet wird:

const fn id<T>(x: T) -> T { x }

const SILLY: () = unsafe {
    (id(Transmute::<usize, Box<String>> { from: 1 }.to), ()).1
};

Ergebnisse in "Destruktoren können zur Kompilierungszeit nicht ausgewertet werden".

Relevanter Implementierungscode ist hier (speziell der Aufruf von restrict ):
https://github.com/rust-lang/rust/blob/5e4603f99066eaf2c1cf19ac3afbac9057b1e177/src/librustc_mir/transform/qualify_consts.rs#L557

Eine bessere Analyse von # 41073 hatte ergeben, dass die Semantik für die Ausführung von Destruktoren bei der Zuordnung zu Unterfeldern von Gewerkschaften nicht ausreichend für die Stabilisierung bereit ist. Weitere Informationen finden Sie in diesem Problem.

Ist es realistisch, Drop Typen in Gewerkschaften einfach vollständig auszuschließen und ManuallyDrop separat (als Lang-Item) zu implementieren? Nach allem, was ich sagen kann, scheint ManuallyDrop die größte Motivation für Drop in Gewerkschaften zu sein, aber das ist ein ganz besonderer Fall.

Ohne ein positives "no drop" -Eigenschaft könnten wir dann sagen, dass eine Gewerkschaft gut geformt ist, wenn jedes Feld entweder Copy oder die Form ManuallyDrop<T> . Das wäre ganz Neben Schritt alle Komplikationen um ein Power- Management , Gewerkschaft Felder zuweisen (wo es jede mögliche Lösung wird voll sein überraschender footguns scheint), und die ManuallyDrop ist eine klare Marker für Programmierer , dass sie zu Griff haben Drop selbst hier. (Die Prüfung könnte intelligenter sein, z. B. könnte sie Produkttypen und nominelle Typen durchlaufen, die in derselben Kiste deklariert sind. Natürlich wäre es positiv zu sagen, dass dieser Typ niemals Drop implementieren wird schöner.)


In der Checkliste im ersten Beitrag werden weder Gewerkschaften ohne Größe noch RFC erwähnt. Wir haben jedoch eine Implementierung , die auf Gewerkschaften mit

Dies steht im Widerspruch zu der Art und Weise, wie Gewerkschaften manchmal in C verwendet werden, was ein "Erweiterungspunkt" ist (IIRC @joshtriplett war derjenige, der dies auf alle Fälle kennen, das bedeutet nicht , dass es nur eine einzige Variante ist!

Die Prüfung könnte intelligenter sein, z. B. durch Produkttypen und durch Nenntypen, die in derselben Kiste deklariert sind.

Dieses Prädikat existiert bereits, ist jedoch bei Generika konservativ, da es kein Merkmal gibt, an das man sich binden kann.
Sie können über std::mem::needs_drop zugreifen (wobei ein Eigenwert verwendet wird, den rustc implementiert).

@eddyb berücksichtigt needs_drop die Vorwärtskompatibilität oder prüft es gerne andere Erstellungen, um festzustellen, ob ihre Typen Drop implementieren? Das Ziel hier ist eine Überprüfung, die niemals von semverkompatiblen Änderungen abweicht, bei denen z. B. das Hinzufügen eines impl Drop zu einer Struktur ohne Typ- oder Lebensdauerparameter und nur private Felder semverkompatibel sind.

@ RalfJung

Dies steht im Widerspruch zu der Art und Weise, wie Gewerkschaften manchmal in C verwendet werden, was ein "Erweiterungspunkt" ist (IIRC @joshtriplett war derjenige, der dies auf alle Fälle

Das ist ein sehr spezifischer Fall.
Es betrifft nur Gewerkschaften im C-Stil (es gibt also keine Destruktoren und alles ist Copy , genau die Teilmenge, die auf Stable verfügbar ist), die aus C-Headern generiert wird.
Wir können solchen Gewerkschaften problemlos ein Feld _dummy: () oder _future: () hinzufügen und weiterhin standardmäßig von einem sichereren "Enum" -Modell profitieren. Eine FFI-Gewerkschaft als "Erweiterungspunkt" muss ohnehin gut dokumentiert werden.

Am 17. April 2018, 10:08:54 Uhr PDT, schrieb Vadim Petrochenkov [email protected] :

Wir können solchen Gewerkschaften leicht ein Feld _dummy: () oder _future: () hinzufügen
und profitieren Sie weiterhin standardmäßig von unserem sichereren "Enum" -Modell.

Ich habe Leute gesehen, die darüber gesprochen haben, Gewerkschaften wie Aufzählungen zu behandeln, für die wir den Diskriminanten einfach nicht kennen, aber nach meinem besten Wissen kenne ich kein tatsächliches Modell oder keine Behandlung von ihnen als solche. In der ursprünglichen Diskussion wollten sogar Nicht-FFI-Gewerkschaften das Modell "Mehrere Varianten gleichzeitig gültig", einschließlich der motivierenden Anwendungsfälle, um überhaupt Nicht-FFI-Gewerkschaften zu wollen.

Das Hinzufügen einer () -Variante zu einer Gewerkschaft sollte nichts ändern, und Gewerkschaften sollten dies nicht tun müssen, um die erwartete Semantik zu erhalten. Gewerkschaften sollten weiterhin eine Tüte voller Dinge sein, wobei Rust keine Ahnung hat, was sie zu einem bestimmten Zeitpunkt enthalten könnten, bis unsicherer Code darauf zugreift.

FFI Union
Ein "Erweiterungspunkt" zu sein, ist etwas, das gut dokumentiert werden muss
wie auch immer.

Wir sollten die Semantik auf jeden Fall so genau wie möglich dokumentieren.

@RalfJung Nein, es verhält sich wie auto trait s und enthüllt alle internen Details.

Derzeit wird unter https://github.com/rust-lang/rust/issues/41073#issuecomment -380291471 über "aktive Felder" diskutiert und Gewerkschaften besucht

Gewerkschaften sollten weiterhin eine Tüte voller Dinge sein, wobei Rust keine Ahnung hat, was sie zu einem bestimmten Zeitpunkt enthalten könnten, bis unsicherer Code darauf zugreift.

Genau so würde ich erwarten, dass Gewerkschaften funktionieren. Sie sind eine erweiterte Funktion, um zusätzliche Leistung zu erzielen und mit C-Code zu interagieren, wo es keine Destruktoren gibt.

Wenn Sie den Inhalt einer Union löschen möchten, sollten Sie ~ für mich in den Typ umwandeln / umwandeln müssen (möglicherweise kann er nicht umgewandelt werden, da er mit einigen nicht verwendeten Bits am Ende für eine andere Variante möglicherweise größer ist) Sie möchten ~ löschen und Zeiger auf die Felder setzen, die gelöscht werden müssen, und std::ptr::drop_in_place , oder die Feldsyntax verwenden, um den Wert zu extrahieren.

Wenn ich nichts über Gewerkschaften wüsste, würde ich erwarten, dass sie so funktionieren:

Beispiel - Darstellung von mem::uninitialized als Gewerkschaft

pub union MaybeValid<T> {
    valid: T,
    invalid: ()
}

impl<T> MaybeValid<T> {
    #[inline] // this should optimize to a no-op
    pub fn from_valid(valid: T) -> MaybeValid<T> {
        MaybeValid { valid }
    }

    pub fn invalid() -> MaybeValid<T> {
        MaybeValid { invalid: () }
    }

   pub fn zeroed() -> MaybeValid<T> {
        // do whatever is necessary here...
        unimplemented!()
    }
}

fn example() {
    let valid_data = MaybeValid::from_valid(1_u8);
    // Destructor of a union always does nothing, but that's OK since our 
    // data type owns nothing.
    drop(valid_data);
    let invalid_data = MaybeValid::invalid();
    // Destructor of a union again does nothing, which means it needs to know 
    // nothing about its surroundings, and can't accidentally try to free unused memory.
    drop(invalid_data);
    let valid_data = MaybeValid::from_valid(String::from("test string"));
    // Now if we dropped `valid_data` we would leak memory, since the string 
    // would never get freed. This is already possible in safe rust using e.g. `Rc`. 
    // `union` is a similarly advanced feature to `Rc` and so new users are 
    // protected by the order in which concepts are introduced to them. This is 
    // still "safe" even though it leaks because it cannot trigger UB.
    //drop(valid_data)
    // Since we know that our union is of a particular form, we can safely 
    // move the value out, in order to run the destructor. I would expect this 
    // to fail if the drop method had run, even though the drop method does 
    // nothing, because that's the way stuff works in rust - once it's dropped
    // you can't use it.
    let _string_to_drop = unsafe { valid_data.valid };
    // No memory leak and all unsafety is encapsulated.
}

Ich werde dies posten und dann bearbeiten, damit ich meine Arbeit nicht verliere.
EDIT @SimonSapin Weg, um Felder zu

Wenn Sie den Inhalt einer Union löschen möchten, müssen Sie ihn in den Typ umwandeln, den Sie löschen möchten (möglicherweise kann er nicht umgewandelt werden, da er mit einigen nicht verwendeten Bits am Ende für eine andere Variante möglicherweise größer ist) oder verwenden Sie die Feldsyntax, um den Wert zu extrahieren

(Wenn Sie es nur fallen lassen möchten, müssen Sie den Wert nicht im Sinne einer Verschiebung extrahieren. Sie können einen Zeiger auf eines der Felder nehmen und std::ptr::drop_in_place .)

Verwandte Themen: Für Konstanten argumentiere ich derzeit, dass mindestens ein Feld einer Vereinigung innerhalb einer Konstante korrekt sein muss: https://github.com/rust-lang/rust/pull/51361 (wenn Sie ein ZST-Feld haben, das das ist immer wahr)

Ich werde dies posten und dann bearbeiten, damit ich meine Arbeit nicht verliere.

Bitte beachten Sie, dass Änderungen nicht in E-Mail-Benachrichtigungen berücksichtigt werden. Wenn Sie wesentliche Änderungen an Ihrem Kommentar vornehmen möchten, sollten Sie stattdessen oder zusätzlich einen neuen Kommentar erstellen.

@derekdreery (und alle anderen) Ich würde mich für Ihr Feedback zu https://internals.rust-lang.org/t/pre-rfc-unions-drop-types-and-manuallydrop/8025 interessieren

Verwandte: Für Konstanten argumentiere ich derzeit, dass mindestens ein Feld einer Vereinigung innerhalb einer Konstante korrekt sein muss: # 51361

Ich habe die Implementierung gesehen, aber das Argument nicht gesehen. ;)

Nun ... Argument von "überhaupt nicht zu überprüfen schien seltsam".

Ich werde gerne jedes Schema implementieren, das wir im const checker entwickeln, aber meine Intuition war immer, dass eine Variante einer Gewerkschaft vollständig korrekt sein muss.

Ansonsten sind Gewerkschaften nur eine hübsche Möglichkeit, einen Typ mit einer bestimmten Größe und Ausrichtung anzugeben, und einige vom Compiler generierte Vorteile beim Umwandeln zwischen einer festen Gruppe von Typen.

Ich denke, Gewerkschaften sind "Taschen mit nicht interpretierten Teilen" mit einer bequemen Möglichkeit, darauf zuzugreifen. Ich sehe überhaupt nichts Seltsames daran, sie nicht zu überprüfen.

AFAIK gibt es tatsächlich einige Anwendungsfälle @joshtriplett, die bei den Berliner

Ich denke, Gewerkschaften sind "Taschen mit nicht interpretierten Teilen" mit einer bequemen Möglichkeit, darauf zuzugreifen. Ich sehe überhaupt nichts Seltsames daran, sie nicht zu überprüfen.

Ich denke immer, dass diese Interpretation etwas gegen den Geist der Sprache verstößt.
An anderen Stellen verwenden wir statische Analysen, um Fußgewehre zu verhindern. Überprüfen Sie, ob nicht auf nicht initialisierte oder geliehene Werte zugegriffen wird. Bei Gewerkschaften, bei denen die Analyse plötzlich deaktiviert ist, schießen Sie bitte.

Ich sehe das als genau den Zweck von union . Ich meine, wir haben auch Rohzeiger, bei denen alle Analysen deaktiviert sind. Gewerkschaften bieten die volle Kontrolle über das Datenlayout, genau wie Rohzeiger die volle Kontrolle über den Speicherzugriff. Beides geht auf Kosten der Sicherheit.

Dies macht auch union einfach . Ich denke, einfach zu sein ist wichtig und noch wichtiger, wenn es um unsicheren Code geht (was bei Gewerkschaften immer der Fall sein wird). Zusätzliche Komplexität sollten wir hier nur akzeptieren, wenn sie greifbare Vorteile bietet.

Wir glauben nicht, dass wir diese Kosten für Gewerkschaften bezahlen müssen, da das Bag-of-Bit-Modell im Vergleich zum Modell der Aufzählung mit unbekannter Variante keine neuen Möglichkeiten bietet.

Die hier in Rede stehende Eigenschaft ist für unsicheren Code mindestens genauso belastend wie ein Schutz. Es gibt keine statische Analyse, die alle Fehler verhindern könnte, die diese Eigenschaft beschädigen könnten, da wir Gewerkschaften für unsicheren Typ Punning 1 verwenden möchten. "Aufzählung mit unbekannter Variante" bedeutet also wirklich, dass Gewerkschaften, die mit Code umgehen, sehr vorsichtig sein müssen, wie sie in die Datei schreiben Union oder Risk Instant UB, ohne die Unsicherheit beim Lesen aus der Union wirklich zu verringern, da das Lesen bereits das Wissen erfordert (über Kanäle, die der Compiler nicht versteht), dass die Bits für die Variante gültig sind, die Sie lesen. Wir können Benutzer nur vor einer Union

1 Angenommen, Tupel sind der Einfachheit halber repr (C), können Sie mit union Foo { a: (bool, u8), b: (u8, bool) } etwas konstruieren, das nur durch Feldzuweisungen ungültig ist.

@rkruppe

Vereinigung Foo {a: (bool, u8), b: (u8, bool)}

Hey, das ist mein Beispiel :)
Und es ist gültig unter dem Modell des RFC 1897 (mindestens eines der "Blatt" -Fragmente bool -1, u8 -1, u8 -2, bool -2 gilt nach Teilzuweisungen).

Gewerkschaften, die mit Code umgehen, müssen sehr vorsichtig sein, wie sie in die Gewerkschaft schreiben oder sofort UB riskieren

Das ist der Punkt des RFC 1897-Modells. Die statische Überprüfung stellt sicher, dass keine sichere Operation (wie Zuweisung oder Teilzuweisung) die Vereinigung in einen ungültigen Zustand versetzen kann, sodass Sie nicht die ganze Zeit über sehr vorsichtig sein müssen und keine sofortige UB erhalten müssen .
Nur gewerkschaftsunabhängige unsichere Operationen wie das Schreiben durch Platzhalter können eine Vereinigung ungültig machen.

Andererseits kann die Vereinigung ohne Bewegungsprüfung sehr leicht in einen ungültigen Zustand versetzt werden.

let u: Union;
let x = u.field; // UB

Das ist der Punkt des RFC 1897-Modells. Die statische Überprüfung stellt sicher, dass kein sicherer Betrieb (wie Zuweisung oder Teilzuweisung) die Vereinigung in einen ungültigen Zustand versetzen kann, sodass Sie nicht die ganze Zeit über sehr vorsichtig sein müssen und keine sofortige UB erhalten müssen .
Nur gewerkschaftsunabhängige unsichere Operationen wie das Schreiben durch Platzhalter können eine Vereinigung ungültig machen.

Sie können automatisch erkennen, dass einige Arten von Schreibvorgängen nicht gegen die zusätzlichen Invarianten verstoßen, die den Gewerkschaften auferlegt wurden, aber es sind immer noch zusätzliche Invarianten, die von den Schriftstellern bestätigt werden müssen. Da das Lesen immer noch unsicher ist und manuell sichergestellt werden muss, dass die Bits für die gelesene Variante gültig sind, hilft dies den Lesern nicht wirklich, sondern erschwert lediglich das Leben der Autoren. Weder "Bag of Bits" noch "Aufzählung mit unbekannter Variante" helfen bei der Lösung des schwierigen Problems der Gewerkschaften: Wie kann sichergestellt werden, dass tatsächlich die Art von Daten gespeichert wird, die Sie lesen möchten?

Wie würde sich die schickere Typprüfung auf das Löschen auswirken? Wenn Sie eine Gewerkschaft erstellen, geben Sie sie an C weiter, die das Eigentum übernimmt. Wird Rost versuchen, die Daten freizugeben, was möglicherweise zu einer doppelten Freigabe führt? Oder würden Sie Drop immer selbst implementieren?

Bearbeiten Es wäre viel cooler, wenn Gewerkschaften wie "Aufzählungen, bei denen die Variante zur Kompilierungszeit statisch überprüft wird" wären, wenn ich den Vorschlag verstanden hätte

edit 2 könnten Gewerkschaften als eine Tüte mit Bits beginnen und später einen sicheren Zugang ermöglichen, während sie abwärtskompatibel sind?

Und es ist gültig nach dem Modell des RFC 1897 (mindestens eines der "Blatt" -Fragmente bool-1, u8-1, u8-2, bool-2 ist nach Teilzuweisungen gültig).

Wenn wir entscheiden, dass dies gültig sein soll, sollte @ oli-obk die Überprüfungen von miri aktualisieren, um dies widerzuspiegeln. Wenn https://github.com/rust-lang/rust/pull/51361 zusammengeführt wird, wird dies von abgelehnt miri.

@petrochenkov Der Teil, den ich nicht verstehe, ist, was uns das kauft. Wir erhalten zusätzliche Komplexität in Bezug auf Implementierung (statische Analyse) und Verwendung (Benutzer müssen immer noch die genauen Regeln kennen). Diese zusätzliche Komplexität führt dazu, dass wir uns bei der Nutzung von Gewerkschaften bereits in einem unsicheren Kontext befinden, sodass die Dinge natürlich komplexer sind. Ich denke, wir sollten eine klare Motivation dafür haben, warum sich diese zusätzliche Komplexität lohnt. Ich halte "es verstößt etwas gegen den Geist der Sprache" nicht für eine klare Motivation.

Das einzige, woran ich denken kann, sind Layoutoptimierungen. In einem "Bag of Bits" -Modell hat eine Gewerkschaft niemals eine Nische. Ich bin jedoch der Meinung, dass dies bessere Adressen sind, wenn der Programmierer mehr manuelle Kontrolle über die Nische erhält, was auch in anderen Fällen nützlich wäre.

Ich glaube, mir fehlt hier etwas Grundlegendes. Ich stimme der @rkruppe zu
Das schwierige Problem bei den Gewerkschaften besteht darin, sicherzustellen, dass die Gewerkschaft derzeit speichert
die Daten, die das Programm lesen möchte.

AFAIK dieses Problem kann jedoch nicht „lokal“ durch statische Analyse gelöst werden. Wir
würde zumindest die gesamte Programmanalyse lesen, und selbst dann wäre es immer noch so
ein schwer zu lösendes Problem.

Also ... gibt es eine Lösung für dieses Problem auf dem Tisch? Oder was macht das?
genaue Lösungen, die vorgeschlagen werden, kaufen uns tatsächlich? Angenommen, ich bekomme eine Gewerkschaft von C,
Was kann das vorgeschlagene tun, ohne das gesamte Rust- und C-Programm zu analysieren?
statische Analysen garantieren tatsächlich für die Leser?

@gnzlbg Ich denke, die einzige Garantie, die wir bekommen würden, ist das, was @petrochenkov oben geschrieben hat

Die statische Überprüfung stellt sicher, dass kein sicherer Betrieb (wie Zuweisung oder Teilzuweisung) die Vereinigung in einen ungültigen Zustand versetzen kann

Andererseits kann die Vereinigung ohne Bewegungsprüfung sehr leicht in einen ungültigen Zustand versetzt werden.

Ihr Vorschlag schützt auch nicht vor schlechten Lesungen, ich denke nicht, dass das möglich ist.

Außerdem stellte ich mir ein sehr einfaches "initialisiertes" Tracking vor, wie "Schreiben in ein beliebiges Feld initialisiert die Vereinigung". Wir würden sowieso etwas brauchen, wenn impl Drop for MyUnion erlaubt ist. Ob gut oder schlecht, wir müssen entscheiden, wann und wo automatische Drop-Calls für eine Gewerkschaft eingefügt werden sollen. Diese Regeln sollten so einfach wie möglich sein, da dies zusätzlicher Code ist, den wir in vorhandenen subtilen unsicheren Code einfügen. Für Gewerkschaften, die Drop implementieren, habe ich mir auch eine Einschränkung vorgestellt, die struct ähnelt und das Schreiben in ein Feld nur erlaubt, wenn die Datenstruktur bereits initialisiert ist.

@derekchiang

Könnten Gewerkschaften als eine Tüte mit Bits beginnen und später einen sicheren Zugang ermöglichen, während sie abwärtskompatibel sind?
Nein. Sobald wir sagen, dass es sich um eine Tüte mit Bits handelt, könnte es unsicheren Code geben, vorausgesetzt, dies ist zulässig.

Ich denke, es ist wertvoll, das Minimum zu überprüfen, um festzustellen, ob eine Union initialisiert ist. Der ursprüngliche RFC hat ausdrücklich angegeben, dass beim Initialisieren oder Zuweisen zu einem Vereinigungsfeld die gesamte Vereinigung initialisiert wird. Darüber hinaus sollte rustc jedoch nicht versuchen, auf den Wert in einer Union zu schließen, den der Benutzer nicht explizit angibt. Eine Union kann einen beliebigen Wert enthalten, einschließlich eines Werts, der für keines ihrer Felder gültig ist.

Ein Anwendungsfall hierfür ist beispielsweise: Betrachten Sie eine mit Tags versehene Union im C-Stil, die in Zukunft explizit um weitere Tags erweitert werden kann. C- und Rust-Code, der diese Vereinigung liest, darf nicht davon ausgehen, dass sie jeden möglichen Feldtyp kennt.

@ RalfJung

Vielleicht sollte ich aus der anderen Richtung beginnen.

Sollte dieser Code funktionieren 1) für Gewerkschaften 2) für Nicht-Gewerkschaften?

let x: T;
let y = x.field;

Für mich ist die Antwort in beiden Fällen offensichtlich "Nein", da dies eine ganze Klasse von Fehlern ist, die Rust verhindern kann und will, unabhängig von der "Vereinigung" von T .

Dies bedeutet, dass der Bewegungsprüfer über ein Schema verfügen sollte, nach dem er diese Unterstützung implementiert. Angesichts der Tatsache, dass der Verschiebungsprüfer (und der Leihprüfer) im Allgemeinen feldspezifisch funktionieren, wäre das einfachste Schema für Gewerkschaften "dieselben Regeln wie für Strukturen + (De-) Initialisierung / Ausleihe eines Feldes, das auch seine Geschwisterfelder (de) initialisiert / ausleiht ".
Diese einfache Regel deckt die gesamte statische Prüfung ab.

Dann ist das Enum-Modell einfach eine Folge der oben beschriebenen statischen Überprüfung + eine weitere Bedingung.
Wenn 1) die Initialisierungsprüfung aktiviert ist und 2) unsicherer Code keine willkürlichen ungültigen Bytes in den Bereich der Union schreibt, ist eines der "Blatt" -Felder der Union automatisch gültig. Dies ist eine dynamische, nicht überprüfbare Garantie (zumindest für Gewerkschaften mit> 1 Feldern und außerhalb von const-evaluator), die sich jedoch vor allem an Personen richtet, die Code lesen.

Dieser Fall zum Beispiel von @joshtriplett

Ein Anwendungsfall hierfür ist beispielsweise: Betrachten Sie eine mit Tags versehene Union im C-Stil, die in Zukunft explizit um weitere Tags erweitert werden kann. C- und Rust-Code, der diese Vereinigung liest, darf nicht davon ausgehen, dass sie jeden möglichen Feldtyp kennt.

Für Menschen, die Code lesen, wäre dies viel klarer, wenn die Gewerkschaft ausdrücklich ein zusätzliches Feld für "mögliche zukünftige Erweiterungen" hätte.

Natürlich können wir die grundlegende statische Initialisierungsprüfung beibehalten, aber die zweite Bedingung ablehnen und zulassen, dass beliebige, möglicherweise ungültige Daten über unsichere Mittel von "Dritten" in die Union geschrieben werden, ohne dass es sich um eine sofortige UB handelt. Dann hätten wir diese dynamische, auf Menschen ausgerichtete Garantie nicht mehr, ich denke nur, das wäre ein Nettoverlust.

@petrochenkov

Sollte dieser Code funktionieren 1) für Gewerkschaften 2) für Nicht-Gewerkschaften?

let x: T;
let y = x.field;

Für mich ist die Antwort in beiden Fällen offensichtlich "Nein", da dies eine ganze Klasse von Fehlern ist, die Rust verhindern kann und will, unabhängig von der "Vereinigung" von T .

Einverstanden ist, dass diese Überprüfung auf nicht initialisierte Werte vernünftig und durchaus machbar erscheint.

Dies bedeutet, dass der Bewegungsprüfer über ein Schema verfügen sollte, nach dem er diese Unterstützung implementiert. Angesichts der Tatsache, dass der Verschiebungsprüfer (und der Leihprüfer) im Allgemeinen feldspezifisch funktionieren, wäre das einfachste Schema für Gewerkschaften "dieselben Regeln wie für Strukturen + (De-) Initialisierung / Ausleihe eines Feldes, das auch seine Geschwisterfelder (de) initialisiert / ausleiht ".
Diese einfache Regel deckt die gesamte statische Prüfung ab.

Bisher einverstanden, vorausgesetzt ich verstehe die Regeln für Strukturen.

Dann ist das Enum-Modell einfach eine Folge der oben beschriebenen statischen Überprüfung + eine weitere Bedingung.
Wenn 1) die Initialisierungsprüfung aktiviert ist und 2) unsicherer Code keine willkürlichen ungültigen Bytes in den Bereich der Union schreibt, ist eines der "Blatt" -Felder der Union automatisch gültig. Dies ist eine dynamische, nicht überprüfbare Garantie (zumindest für Gewerkschaften mit> 1 Feldern und außerhalb von const-evaluator), die sich jedoch vor allem an Personen richtet, die Code lesen.

Diese zusätzliche Bedingung gilt nicht für Gewerkschaften.

Dieser Fall zum Beispiel von @joshtriplett

Ein Anwendungsfall hierfür ist beispielsweise: Betrachten Sie eine mit Tags versehene Union im C-Stil, die in Zukunft explizit um weitere Tags erweitert werden kann. C- und Rust-Code, der diese Vereinigung liest, darf nicht davon ausgehen, dass sie jeden möglichen Feldtyp kennt.

Für Menschen, die Code lesen, wäre dies viel klarer, wenn die Gewerkschaft ausdrücklich ein zusätzliches Feld für "mögliche zukünftige Erweiterungen" hätte.

So funktionieren weder Gewerkschaften noch Rust-Gewerkschaften. (Und ich würde fragen, ob es klarer wäre oder einfach, ob es anderen Erwartungen entspricht.) Wenn dies geändert würde, wären die Rust-Gewerkschaften für einige der Zwecke, für die sie entworfen und vorgeschlagen wurden, nicht mehr geeignet.

Natürlich können wir die grundlegende statische Initialisierungsprüfung beibehalten, aber die zweite Bedingung ablehnen und zulassen, dass beliebige, möglicherweise ungültige Daten über unsichere Mittel von "Dritten" in die Union geschrieben werden, ohne dass es sich um eine sofortige UB handelt. Dann hätten wir diese dynamische, auf Menschen ausgerichtete Garantie nicht mehr, ich denke nur, das wäre ein Nettoverlust.

Diese "unsicheren" Drittanbieter "bedeuten" eine Gewerkschaft von FFI erhalten ", was ein vollständig gültiger Anwendungsfall ist.

Hier ist ein konkretes Beispiel:

union Event {
    event_id: u32,
    event1: Event1,
    event2: Event2,
    event3: Event3,
}

struct Event1 {
    event_id: u32, // always EVENT1
    // ... more fields ...
}
// ... more event structs ...

match u.event_id {
    EVENT1 => { /* ... */ }
    EVENT2 => { /* ... */ }
    EVENT3 => { /* ... */ }
    _ => { /* unknown event */ }
}

Das ist ein völlig gültiger Code, den Menschen mit Gewerkschaften schreiben können und werden.

@petrochenkov

Sollte dieser Code funktionieren 1) für Gewerkschaften 2) für Nicht-Gewerkschaften?
Für mich ist die Antwort in beiden Fällen offensichtlich "Nein", da dies eine ganze Klasse von Fehlern ist, die Rust verhindern kann und will, unabhängig von der "Vereinigung" von T.

Gut für mich.

Das einfachste Schema für Gewerkschaften wäre "die gleichen Regeln wie für Strukturen + (De) Initialisierung / Ausleihe eines Feldes, auch (De) Initialisierung / Ausleihe seiner Geschwisterfelder".

Woah. Die Strukturregeln sind sinnvoll, da sie alle auf der Tatsache beruhen, dass verschiedene Felder disjunkt sind . Sie können diese Grundannahme nicht einfach ungültig machen und trotzdem dieselben Regeln anwenden. Die Tatsache, dass Sie einen Nachtrag zu den Regeln benötigen, zeigt dies. Ich würde niemals erwarten, dass Gewerkschaften ähnlich wie Strukturen überprüft werden. Wenn überhaupt, könnte man erwarten, dass sie ähnlich wie Enums überprüft werden - aber das kann natürlich nicht funktionieren, da auf Enums nur über Match zugegriffen werden kann.

Wenn 1) die Initialisierungsprüfung aktiviert ist und 2) unsicherer Code keine willkürlichen ungültigen Bytes in den Bereich der Union schreibt, ist eines der "Blatt" -Felder der Union automatisch gültig. Dies ist eine dynamische, nicht überprüfbare Garantie (zumindest für Gewerkschaften mit> 1 Feldern und außerhalb von const-evaluator), die sich jedoch vor allem an Personen richtet, die Code lesen.

Ich halte es für äußerst wünschenswert, dass die grundlegenden Gültigkeitsannahmen dynamisch überprüfbar sind (gegebene Typinformationen). Dann können wir sie während der CTFE in miri überprüfen, wir können sie sogar während "voller" miri-Läufe überprüfen (z. B. einer Testsuite), wir können schließlich eine Art Desinfektionsmittel oder vielleicht einen Modus haben, in dem Rust debug_assert! ausgibt an kritischen Stellen, um die Gültigkeitsinvarianten zu überprüfen.
Ich denke, die Erfahrung mit den nicht überprüfbaren Regeln von C gibt genügend Hinweise darauf, dass diese problematisch sind. Normalerweise besteht der erste Schritt, um die Regeln tatsächlich zu verstehen und zu klären, darin, einen dynamisch überprüfbaren Weg zu finden, um sie auszudrücken. Selbst für Parallelitätsspeichermodelle werden "dynamisch überprüfbare" Varianten (Betriebssemantik, die alles in Bezug auf die schrittweise Ausführung einer virtuellen Maschine erklärt) angezeigt und scheinen die einzige Möglichkeit zu sein, langjährige offene Probleme der Axiomatik zu lösen Modelle, die zuvor verwendet wurden ("Ouf of Thin Air Problem" ist hier ein Schlüsselwort).

Ich kann kaum übertreiben, wie wichtig es meiner Meinung nach ist, dynamisch überprüfbare Regeln zu haben. Ich denke, wir sollten versuchen, 0 nicht überprüfbare Fälle von UB zu haben. (Wir sind noch nicht da, aber es ist das Ziel, das wir haben sollten.) Dies ist der einzig verantwortungsvolle Weg, um UB in Ihrer Sprache zu haben. Alles andere ist ein Fall von Compiler- / Sprachautoren, die ihr Leben auf Kosten aller erleichtern muss mit den Folgen leben. (Ich arbeite derzeit an dynamisch überprüfbaren Regeln für Aliasing und unformatierte Zeigerzugriffe.)
Auch wenn dies für mich das einzige Problem wäre, ist "nicht dynamisch überprüfbar" ein ausreichender Grund, diesen Ansatz nicht anzuwenden.

Trotzdem sehe ich keinen fundamentalen Grund, warum dies nicht überprüfbar sein sollte: Gehen Sie für jedes Byte in der Union alle Varianten durch, um zu sehen, welche Werte für dieses Byte in dieser Variante zulässig sind, und nehmen Sie die Union (heh;)) von allen dieser Sätze. Eine Folge von Bytes ist für eine Vereinigung gültig, wenn jedes Byte gemäß dieser Definition gültig ist.
Dies ist jedoch ziemlich schwierig, um tatsächlich eine Prüfung durchzuführen - bei weitem die komplexeste grundlegende Typgültigkeitsinvariante, die wir in Rust haben würden. Das ist eine direkte Folge der Tatsache, dass diese Gültigkeitsregel etwas schwierig zu beschreiben ist, weshalb ich sie nicht mag.

Natürlich können wir die grundlegende statische Initialisierungsprüfung beibehalten, aber die zweite Bedingung ablehnen und zulassen, dass beliebige, möglicherweise ungültige Daten über unsichere Mittel von "Dritten" in die Union geschrieben werden, ohne dass es sich um eine sofortige UB handelt. Dann hätten wir diese dynamische, auf Menschen ausgerichtete Garantie nicht mehr, ich denke nur, das wäre ein Nettoverlust.

Was kauft uns diese Garantie? Wo hilft es eigentlich? Im Moment sehe ich nur, dass jeder hart arbeiten und vorsichtig sein muss, um es aufrechtzuerhalten. Ich sehe keinen Nutzen daraus, dass wir, die Menschen, davon profitieren.

@ Joshtriplett

Stellen Sie sich eine Union mit C-Tags vor, die in Zukunft explizit um weitere Tags erweitert werden kann. C- und Rust-Code, der diese Vereinigung liest, darf nicht davon ausgehen, dass sie jeden möglichen Feldtyp kennt.

Das von @petrochenkov vorgeschlagene __non_exhaustive: () -Feld hinzugefügt wird. Ich denke jedoch nicht, dass dies notwendig sein sollte. Es ist denkbar, dass Bindungsgeneratoren ein solches Feld hinzufügen könnten.

@ RalfJung

Dies ist dynamisch nicht überprüfbar (zumindest für Gewerkschaften mit> 1 Feldern und außerhalb von const-evaluator)

Ich halte es für äußerst wünschenswert, dass die grundlegenden Gültigkeitsannahmen dynamisch überprüfbar sind

Eine Klarstellung: Ich meinte "nicht standardmäßig" / "im Release-Modus" deaktivierbar, natürlich kann es im "langsamen Modus" mit einigen zusätzlichen Instrumenten überprüft werden, aber Sie haben bereits besser darüber geschrieben, als ich konnte.

@ RalfJung

Das von @petrochenkov vorgeschlagene

Ja, ich habe verstanden, dass dies der Vorschlag war.

Ich denke jedoch nicht, dass dies notwendig sein sollte. Es ist denkbar, dass Bindungsgeneratoren ein solches Feld hinzufügen könnten.

Sie könnten, aber sie müssten es systematisch zu jeder einzelnen Gewerkschaft hinzufügen.

Ich habe noch kein Argument dafür gefunden, warum es sinnvoll ist, primäre Anwendungsfälle von Gewerkschaften zugunsten eines nicht spezifizierten Anwendungsfalls zu brechen, der davon abhängt, welche Bitmuster sie enthalten können.

@ Joshtriplett

primäre Anwendungsfälle von Gewerkschaften

Mir ist überhaupt nicht klar, warum dies der primäre Anwendungsfall ist.
Es kann für repr(C) Gewerkschaften zutreffen, wenn Sie davon ausgehen, dass alle Verwendungen von Gewerkschaften für getaggte Gewerkschaften / "Rust enum emulation" in FFI Erweiterbarkeit voraussetzen (was nicht wahr ist), aber von dem, was ich gesehen habe, Verwendungen von repr(Rust) Gewerkschaften (Drop Control, Intialization Control, Transmutes) erwarten nicht, dass plötzlich "unerwartete Varianten" in ihnen auftauchen.

@petrochenkov Ich sagte nicht " den primären Anwendungsfall brechen", ich sagte "den primären Anwendungsfall brechen". FFI ist einer der Hauptanwendungsfälle von Gewerkschaften.

und nimm die Vereinigung (heh;)) all dieser Sätze

Die Aussage, dass "die möglichen Werte einer Union die Vereinigung der möglichen Werte aller ihrer möglichen Varianten sind", ist mit Sicherheit attraktiv.

Wahr. Dies ist jedoch nicht der Vorschlag - wir sind uns alle einig, dass Folgendes legal sein sollte:

union F {
  x: (u8, bool),
  y: (bool, u8),
}
fn foo() -> F {
  let mut f = F { x: (5, false) };
  unsafe { f.y.1 = 17; }
  f
}

Eigentlich denke ich, dass es ein Fehler ist, dass dies sogar unsafe erfordert.

Die Gewerkschaft muss also zumindest byteweise genommen werden.
Ich denke auch nicht, dass "attraktive Offensichtlichkeit" allein ein hinreichend guter Grund ist. Jede Invariante, für die wir uns entscheiden, stellt eine erhebliche Belastung für unsichere Code-Autoren dar. Wir sollten konkrete Vorteile haben, die wir wiederum erhalten.

@ RalfJung

Eigentlich denke ich, dass es ein Fehler ist, dass dies sogar unsicher erfordert.

Ich weiß nichts über die neue MIR-basierte Implementierung des Unsicherheitsprüfers, aber in der alten HIR-basierten Implementierung war dies sicherlich eine Einschränkung / Vereinfachung des Prüfers - nur Ausdrücke der Form expr1.field = expr2 wurden auf ein mögliches Feld analysiert Zuweisung "Opt-out für Unsicherheit, alles andere wurde konservativ als generischer" Feldzugang "behandelt, der für Gewerkschaften unsicher ist.

Beantwortung des Kommentars unter https://github.com/rust-lang/rust/issues/52786#issuecomment -408645420:

Die Idee ist also, dass der Compiler immer noch nichts über den Vertrag von Wrap<T> weiß und zB keine Layoutoptimierungen durchführen kann. Ok, diese Position wird verstanden.
Dies bedeutet, dass intern innerhalb des Moduls von Wrap die Implementierung des Moduls Wrap<T> beispielsweise vorübergehend "unerwartete Werte" in das Modul schreiben kann, wenn sie nicht an Benutzer weitergegeben werden. und der Compiler wird mit ihnen einverstanden sein.

Ich bin mir jedoch nicht sicher, wie genau der Teil des Vertrags von Wrap über das Fehlen unerwarteter Werte mit dem Datenschutz vor Ort zusammenhängt.

Erstens können unerwartete Werte, unabhängig davon, ob es sich um private oder öffentliche Felder handelt, nicht direkt über diese Felder geschrieben werden. Sie benötigen dazu so etwas wie einen Rohzeiger oder Code auf der anderen Seite von FFI. Dies kann ohne Feldzugriff erfolgen, indem Sie lediglich einen Zeiger auf die gesamte Union haben. Wir müssen uns dem also aus einer anderen Richtung nähern als dem Zugang zu einem Feld, das eingeschränkt ist.

Wie ich Ihren Kommentar interpretiere, besteht der Ansatz darin zu sagen, dass ein privates Feld (in Union oder einer Struktur keine Rolle spielt) eine willkürliche Invariante impliziert, die dem Benutzer unbekannt ist, sodass alle Operationen, die dieses Feld ändern (direkt oder durch wilde Zeiger, dies nicht tun t matter) führen zu UB, weil sie möglicherweise diese nicht spezifizierte Invariante brechen können.

Dies bedeutet, dass wenn eine Union ein einzelnes privates Feld hat, ihr Implementierer (aber nicht der Compiler) davon ausgehen kann, dass kein Dritter einen unerwarteten Wert in diese Union schreibt.
Dies ist in gewissem Sinne eine "Standard-Gewerkschaftsdokumentationsklausel" für den Benutzer:
- (Standard) Wenn eine Gewerkschaft ein privates Feld hat, können Sie
- Sie können aber Müll in eine Vereinigung schreiben , wenn seine docs es ausdrücklich verbieten.

Wenn eine Gewerkschaft unerwartete Werte verbieten möchte, während sie dennoch pub Zugriff auf ihre erwarteten Felder gewährt (z. B. wenn diese Felder keine eigenen Invarianten haben), kann sie dies dennoch durch Dokumentation tun, weshalb das "es sei denn" in Die zweite Klausel ist notwendig.

@ RalfJung
Beschreibt dies Ihre Position genau?

Wie werden solche Szenarien behandelt?

mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

Wie ich Ihren Kommentar interpretiere, besteht der Ansatz darin zu sagen, dass ein privates Feld (in Union oder einer Struktur keine Rolle spielt) eine willkürliche Invariante impliziert, die dem Benutzer unbekannt ist, sodass alle Operationen, die dieses Feld ändern (direkt oder durch wilde Zeiger, dies nicht tun t matter) führen zu UB, weil sie möglicherweise diese nicht spezifizierte Invariante brechen können.

Nein, das habe ich nicht gemeint.

Es gibt mehrere Invarianten. Ich weiß nicht, wie viele wir brauchen werden, aber es werden mindestens zwei sein (und ich habe keine guten Namen für sie):

  • Die "Invariante auf Layoutebene" (oder "syntaktische Invariante") eines Typs wird vollständig durch die syntaktische Form des Typs definiert. Dies sind Dinge wie " &mut T ist nicht NULL und ausgerichtet", " bool ist 0 oder 1 ", " ! kann nicht existieren". Auf dieser Ebene ist *mut T dasselbe wie usize - beide erlauben jeden Wert (oder vielleicht jeden initialisierten Wert, aber diese Unterscheidung ist für eine andere Diskussion). Wir werden schließlich ein Dokument haben, in dem diese Invarianten für alle Typen durch strukturelle Rekursion beschrieben werden: Die Invariante einer Struktur auf Layoutebene besteht darin, dass alle ihre Felder ihre Invariante beibehalten usw. Die Sichtbarkeit spielt hier keine Rolle.
Violating the layout-level invariant is instantaneous UB. This is a statement we can make because we have defined this invariant in very simple terms, and we make it part of the definition of the language itself. We can then exploit this UB (and we already do), e.g. to perform enum layout optimizations.
  • Die "benutzerdefinierte Invariante auf Typebene" (oder "semantische Invariante") eines Typs wird von demjenigen ausgewählt, der den Typ implementiert. Der Compiler kann diese Invariante nicht kennen, da wir keine Sprache haben, um sie auszudrücken, und dasselbe gilt für die Sprachdefinition. Wir können nicht gegen diese invariante UB verstoßen, da wir nicht einmal sagen können, was diese Invariante ist! Die Tatsache, dass es sogar möglich ist , benutzerdefinierte Invarianten zu haben, ist ein Merkmal jedes nützlichen Typsystems: Abstraktion. Ich habe mehr darüber in einem früheren Blog-Beitrag geschrieben .

    Die Verbindung zwischen der benutzerdefinierten semantischen Invariante und UB besteht darin, dass wir erklären, dass unsicherer Code möglicherweise davon abhängt , dass Vec . Beachten Sie, dass ich falsch gesagt habe (ich verwende manchmal den Begriff unsound ) - aber nicht undefiniertes Verhalten! Ein weiteres Beispiel, um diesen Unterschied zu demonstrieren (wirklich dasselbe Beispiel), ist die Diskussion über Aliasing-Regeln für &mut ZST . Das Erstellen eines baumelnden, gut ausgerichteten Nicht-Null- &mut ZST ist niemals unmittelbar UB, aber es ist immer noch falsch / nicht gesund, da man möglicherweise unsicheren Code schreibt, der darauf beruht, dass dies nicht geschieht.

Es wäre schön, diese beiden Konzepte aufeinander abzustimmen, aber ich halte es nicht für praktisch. Erstens verwendet die Definition der benutzerdefinierten semantischen Invariante für einige Typen (Funktionszeiger, Dyn-Merkmale) tatsächlich die Definition von UB in der Sprache. Diese Definition wäre zirkulär, wenn wir sagen wollten, dass es UB ist, jemals die benutzerdefinierte semantische Invariante zu verletzen. Zweitens würde ich es vorziehen, wenn die Definition unserer Sprache und ob eine bestimmte Ausführungsspur UB aufweist, eine entscheidbare Eigenschaft wäre. Semantische, benutzerdefinierte Invarianten sind häufig nicht entscheidbar.


Ich bin mir jedoch nicht sicher, wie genau der Teil des Wraps-Vertrags über das Fehlen unerwarteter Werte mit dem Datenschutz vor Ort zusammenhängt.

Wenn ein Typ seine benutzerdefinierte Invariante auswählt, muss er im Wesentlichen sicherstellen, dass alles, was sicherer Code kann, die Invariante beibehält . Das Versprechen ist schließlich, dass die Verwendung der sicheren API dieses Typs niemals zu UB führen kann. Dies gilt sowohl für Strukturen als auch für Gewerkschaften. Sicherer Code kann unter anderem auf öffentliche Felder zugreifen, von denen diese Verbindung stammt.

Beispielsweise kann ein öffentliches Feld einer Struktur keine benutzerdefinierte Invariante haben, die sich von der benutzerdefinierten Invariante des Feldtyps unterscheidet : Schließlich kann jeder sichere Benutzer beliebige Daten in dieses Feld schreiben oder aus dem Feld lesen und "gut" erwarten. Daten. Eine Struktur, in der alle Felder öffentlich sind, kann sicher erstellt werden, wodurch das Feld weiter eingeschränkt wird.

Eine Gewerkschaft mit einem öffentlichen Feld ... nun, das ist etwas interessant. Das Lesen von Gewerkschaftsfeldern ist sowieso unsicher, daher ändert sich dort nichts. Das Schreiben von Vereinigungsfeldern ist sicher, daher muss eine Vereinigung mit einem öffentlichen Feld in der Lage sein, beliebige Daten zu verarbeiten, die die benutzerdefinierte Invariante des Feldtyps erfüllen, die in das Feld eingefügt wird. Ich bezweifle, dass dies sehr nützlich sein wird ...

Um es noch einmal zusammenzufassen: Wenn Sie eine benutzerdefinierte Invariante auswählen, liegt es in Ihrer Verantwortung, sicherzustellen, dass fremder sicherer Code diese Invariante nicht brechen kann (und Sie verfügen über Tools wie private Felder, um dies zu erreichen). Es liegt in der Verantwortung von fremdem unsicherem Code, Ihre Invariante nicht zu verletzen, wenn dieser Code etwas tut, was sicherer Code nicht kann.


Dies bedeutet, dass intern innerhalb des Wrap-Moduls die Implementierung von Wrap erfolgtDas Modul kann beispielsweise vorübergehend "unerwartete Werte" in das Modul schreiben, wenn es sie nicht an Benutzer weitergibt, und der Compiler ist mit ihnen einverstanden.

Richtig. (Panik-Sicherheit ist hier ein Problem, aber Sie sind sich wahrscheinlich bewusst). Dies ist genau so, wie ich es in Vec sicher tun kann

let sz = self.size;
self.size = 1337;
self.size = sz;

und es gibt keine UB.


mod m {
    union MyPrivateUnion { /* private fields */ }
    extern {
        fn my_private_ffi_function() -> MyPrivateUnion; // Can return garbage (?)
    }
}

In Bezug auf die syntaktische Layoutinvariante kann my_private_ffi_function alles tun (vorausgesetzt, der Funktionsaufruf ABI und die Signatur stimmen überein). In Bezug auf die semantische benutzerdefinierte Invariante ist dies im Code nicht sichtbar. Wer dieses Modul geschrieben hat, hat eine Invariante im Sinn. Er sollte diese neben seiner Vereinigungsdefinition dokumentieren und dann sicherstellen, dass die FFI-Funktion einen Wert zurückgibt, der die Invariante erfüllt .

Ich habe schließlich diesen Blog-Beitrag darüber geschrieben, ob und wann &mut T initialisiert werden muss und welche zwei Arten von Invarianten ich oben erwähnt habe.

Gibt es hier noch etwas zu verfolgen, das nicht bereits von https://github.com/rust-lang/rust/issues/55149 abgedeckt wird, oder sollten wir schließen?

E0658 zeigt hier noch:

Fehler [E0658]: Gewerkschaften mit Nicht- Copy -Feldern sind instabil (siehe Problem # 32836)

Dies spielt derzeit eine schreckliche Rolle bei Atomics, da sie Copy nicht implementieren. Kennt jemand eine Problemumgehung?

Wenn https://github.com/rust-lang/rust/issues/55149 implementiert ist, können Sie ManuallyDrop<AtomicFoo> in einer Union verwenden. Bis dahin besteht die einzige Problemumgehung darin, Nightly zu verwenden (oder nicht union und eine Alternative zu finden).

Wenn dies implementiert ist, sollten Sie nicht einmal ManuallyDrop benötigen. Immerhin weiß rustc, dass Atomic* Drop nicht implementiert.

Ich beauftrage mich, das Tracking-Problem auf das neue umzustellen.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen