Also stieß ich auf dieses Code-Snippet , das zeigt, wie man "unbewegliche" Typen in Rust erstellt - Bewegungen werden verhindert, weil die Compiler behandelt das Objekt für seine gesamte Lebensdauer als ausgeliehen.
%Vor%Ich verstehe noch nicht, wie das funktioniert. Meine Vermutung ist, dass die Lebensdauer des borrow-pointer-Arguments gezwungen ist, die Lebensdauer des Lock-Feldes zu erreichen. Das Seltsame ist, dass dieser Code auf die gleiche Weise funktioniert, wenn:
ContravariantLifetime<'a>
in CovariantLifetime<'a>
oder in InvariantLifetime<'a>
. lock
. Aber wenn ich die Cell
entferne und einfach lock: marker::ContravariantLifetime<'a>
direkt benutze, so:
Dann darf sich das Objekt "Unbeweglich" bewegen. Warum sollte das sein?
Die wahre Antwort besteht aus einer mäßig komplexen Betrachtung der Lebensdauer Varianz , mit ein paar irreführenden Ergebnissen Aspekte des Codes, die aussortiert werden müssen.
Für den folgenden Code ist 'a
eine beliebige Lebensdauer, 'small
ist eine beliebige Lebensdauer, die kleiner ist als 'a
(dies kann durch die Einschränkung 'a: 'small
ausgedrückt werden), und 'static
wird als verwendet das häufigste Beispiel einer Lebensdauer, die größer ist als 'a
.
Hier sind die Fakten und Schritte, die bei der Betrachtung zu beachten sind:
Normalerweise sind Lebenszeiten kontravariant ; &'a T
ist kontravariant in Bezug auf 'a
(wie auch T<'a>
in Abwesenheit von Varianzmarkern), was bedeutet, dass es, wenn Sie &'a T
haben, OK ist, eine längere Lebensdauer als 'a
zu ersetzen, z. Sie können an einem solchen Ort ein &'static T
speichern und behandeln, als wäre es ein &'a T
(Sie dürfen die Lebensdauer verkürzen).
An einigen Orten können Lebenszeiten invariant sein ; Das häufigste Beispiel ist &'a mut T
, das in Bezug auf 'a
invariant ist, was bedeutet, dass wenn Sie &'a mut T
haben, Sie nicht &'small mut T
darin speichern können (das Borgen reicht nicht lange genug), sondern Sie kann auch keine &'static mut T
darin speichern, da dies zu Problemen beim Speichern der Referenz führen würde, da es vergessen würde, dass tatsächlich länger lebte und Sie daher mehrere simultane veränderbare Referenzen erhalten könnten erstellt werden.
Ein Cell
enthält ein UnsafeCell
; Was nicht so offensichtlich ist, ist, dass UnsafeCell
magisch ist und mit dem Compiler für spezielle Behandlung als das Sprachelement namens "unsicher" verknüpft wird. Wichtig ist, dass UnsafeCell<T>
in Bezug auf T
invariant ist, aus ähnlichen Gründen wie Invarianz von &'a mut T
in Bezug auf 'a
.
Somit verhält sich Cell<any lifetime variancy marker>
tatsächlich genauso wie Cell<InvariantLifetime<'a>>
.
Außerdem müssen Sie Cell
nicht mehr verwenden. Sie können einfach InvariantLifetime<'a>
verwenden.
Zurück zum Beispiel mit dem entfernten Cell
-Wrapping und einem ContravariantLifetime
(entspricht eigentlich nur der Definition von struct Unmovable<'a>;
, da die Kontravarianz der Standardwert ist, wie auch die Copy
-Implementierung): Warum lässt es zu? den Wert verschieben? Ich muss gestehen, dass ich diesen speziellen Fall noch nicht kenne und ich würde mir selbst ein wenig helfen, zu verstehen, warum das erlaubt ist. Es scheint von hinten nach vorne, dass die Kovarianz es erlauben würde, dass die Sperre kurzlebig ist, aber dass Kontravarianz und Invarianz nicht funktionieren würden, aber in der Praxis scheint es, dass nur die Invarianz die gewünschte Funktion erfüllt.
Wie auch immer, hier ist das Endergebnis. Cell<ContravariantLifetime<'a>>
wird in InvariantLifetime<'a>
geändert und das ist die einzige funktionale Änderung, die die lock
-Methode wie gewünscht funktioniert und ein Borgen mit einer invarianten Lebensdauer annimmt. (Eine andere Lösung wäre, lock
take &'a mut self
zu haben, denn eine veränderbare Referenz ist, wie bereits diskutiert, invariant; dies ist jedoch unterlegen, da sie eine unnötige Wandlungsfähigkeit erfordert.)
Noch etwas zu erwähnen: Die Inhalte der Methoden lock
und new_in
sind völlig überflüssig. Der Rumpf einer Funktion ändert niemals das statische Verhalten des Compilers; nur die Unterschrift zählt. Die Tatsache, dass der Lebensdauerparameter 'a
als invariant markiert ist, ist der Schlüsselpunkt. Also ist der ganze "construct% Unmovable
-Objekt und Aufruf lock
darauf" Teil von new_in
völlig überflüssig. Ebenso war das Einstellen des Inhalts der Zelle in lock
Zeitverschwendung. (Beachten Sie, dass es wieder die Invarianz von 'a
in Unmovable<'a>
ist, die new_in
funktioniert, nicht die Tatsache, dass es sich um eine veränderbare Referenz handelt.)
Ein interessantes Problem! Hier ist mein Verständnis davon ...
Hier ist ein weiteres Beispiel, das Cell
nicht verwendet:
( Laufstall )
Der wichtige Trick hier ist, dass die Struktur sich selbst ausleihen muss. Sobald wir das getan haben, kann es nicht mehr bewegt werden, da jede Bewegung den Kredit ungültig machen würde. Dies ist nicht konzeptionell anders als jede andere Art von Darlehen:
%Vor% Das einzige ist, dass Sie eine Möglichkeit brauchen, die Struktur ihre eigene Referenz enthalten zu lassen, was zur Konstruktionszeit nicht möglich ist. In meinem Beispiel wird Option
verwendet, wofür &mut self
benötigt wird, und im verknüpften Beispiel wird Cell
verwendet, was eine interne Änderbarkeit und nur &self
ermöglicht.
Beide Beispiele verwenden einen Lebenszeitmarker, da das System die Lebenszeit verfolgen kann, ohne sich um eine bestimmte Instanz kümmern zu müssen.
Schauen wir uns Ihren Konstruktor an:
%Vor% Hier wird die in lock
angegebene Lebensdauer vom Aufrufer gewählt und endet als normale Lebensdauer der Unmovable
struct. Es gibt kein Eigen leihen.
Sehen wir uns als nächstes Ihre Lock-Methode an:
%Vor%Hier weiß der Compiler, dass sich die Lebensdauer nicht ändern wird. Wenn wir es jedoch veränderlich machen:
%Vor% Bam! Es ist wieder gesperrt. Dies liegt daran, dass der Compiler weiß, dass sich die internen Felder ändern können. Wir können dies tatsächlich auf unsere Option
-Variante anwenden und den Körper von lock_it
entfernen!
Tags und Links rust