2015-02-06 9 views
9

So lief ich in this code snippet, die zeigt, wie man "unbewegliche" Typen in Rust erstellt - Bewegungen werden verhindert, weil der Compiler das Objekt für seine gesamte Lebensdauer als geliehen behandelt.Warum wird eine Zelle zum Erstellen unbeweglicher Objekte verwendet?

use std::cell::Cell; 
use std::marker; 

struct Unmovable<'a> { 
    lock: Cell<marker::ContravariantLifetime<'a>>, 
    marker: marker::NoCopy 
} 

impl<'a> Unmovable<'a> { 
    fn new() -> Unmovable<'a> { 
     Unmovable { 
      lock: Cell::new(marker::ContravariantLifetime), 
      marker: marker::NoCopy 
     } 
    } 
    fn lock(&'a self) { 
     self.lock.set(marker::ContravariantLifetime); 
    } 
    fn new_in(self_: &'a mut Option<Unmovable<'a>>) { 
     *self_ = Some(Unmovable::new()); 
     self_.as_ref().unwrap().lock(); 
    } 
} 

fn main(){ 
    let x = Unmovable::new(); 
    x.lock(); 

    // error: cannot move out of `x` because it is borrowed 
    // let z = x; 

    let mut y = None; 
    Unmovable::new_in(&mut y); 

    // error: cannot move out of `y` because it is borrowed 
    // let z = y; 

    assert_eq!(std::mem::size_of::<Unmovable>(), 0) 
} 

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, dieser Code weiterhin in der gleichen Art und Weise arbeiten, wenn:

  • ich ContravariantLifetime<'a>-CovariantLifetime<'a> ändern oder zu InvariantLifetime<'a>.
  • Ich entferne den Körper der lock Methode.

Aber, wenn ich die Cell, entfernen und lock: marker::ContravariantLifetime<'a> nur direkt verwenden, da so:

use std::marker; 

struct Unmovable<'a> { 
    lock: marker::ContravariantLifetime<'a>, 
    marker: marker::NoCopy 
} 

impl<'a> Unmovable<'a> { 
    fn new() -> Unmovable<'a> { 
     Unmovable { 
      lock: marker::ContravariantLifetime, 
      marker: marker::NoCopy 
     } 
    } 
    fn lock(&'a self) { 
    } 
    fn new_in(self_: &'a mut Option<Unmovable<'a>>) { 
     *self_ = Some(Unmovable::new()); 
     self_.as_ref().unwrap().lock(); 
    } 
} 

fn main(){ 
    let x = Unmovable::new(); 
    x.lock(); 

    // does not error? 
    let z = x; 

    let mut y = None; 
    Unmovable::new_in(&mut y); 

    // does not error? 
    let z = y; 

    assert_eq!(std::mem::size_of::<Unmovable>(), 0) 
} 

Dann wird das "unverrückbar" Objekt bewegen darf. Warum sollte das sein?

Antwort

3

Die wahre Antwort besteht aus einer mäßig komplexen Betrachtung der Lebenszeit variancy, mit ein paar irreführenden Aspekten des Codes, die aussortiert werden müssen.

Für den folgenden Code ist 'a eine willkürliche Lebensdauer 'small eine willkürliche Laufzeit ist, die kleiner als 'a ist (dies wird durch den Zwang 'a: 'small ausgedrückt werden kann) und 'static wird als das häufigste Beispiel einer Lebenszeit verwendet wird, die ist größer als 'a.

Hier sind die Fakten und die Schritte in der Überlegung folgen:

  • Normalerweise Lebensdauern sind kontra; &'a T ist kontravariant in Bezug auf 'a (wie T<'a> in Abwesenheit von Varianzmarker), was bedeutet, dass, wenn Sie eine &'a T haben, es OK ist, eine längere Lebensdauer als 'a, z. Sie können an einem solchen Ort eine &'static T speichern und behandeln, als wäre es eine &'a T (Sie dürfen die Lebensdauer verkürzen).

  • An einigen Stellen können die Lebensdauern invariant sein; Das häufigste Beispiel ist &'a mut T, das in Bezug auf 'a invariant ist, was bedeutet, dass wenn Sie eine &'a mut T haben, können Sie keine &'small mut T darin speichern (das Borgen lebt nicht lange genug), aber Sie können auch kein &'static mut T darin speichern, weil das Problem für die gespeicherte Referenz verursachen würde, da es vergessen würde, dass es länger gelebt wird, und so am Ende mit mehreren simultanen veränderbaren Referenzen erstellt werden könnte.

  • Eine Cell enthält eine UnsafeCell; Was nicht so offensichtlich ist, ist, dass UnsafeCell magisch ist, mit dem Compiler für spezielle Behandlung als das Sprachelement namens "unsicher" verdrahtet wird. Wichtig ist, UnsafeCell<T> ist Invariante in Bezug auf T, für ähnliche Arten von Gründen der Invarianz von &'a mut T in Bezug auf 'a.

  • So verhält sich Cell<any lifetime variancy marker> tatsächlich genauso wie .

  • Darüber hinaus müssen Sie nicht mehr Cell mehr verwenden; Sie können einfach InvariantLifetime<'a> verwenden.

  • Rückkehr zum Beispiel mit der Cell Verpackung entfernt und ein ContravariantLifetime (tatsächlich entspricht nur struct Unmovable<'a>; Definition für Kontra wird der Standard wie keine Copy Implementierung): Warum erlaubt es, den Wert zu 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 zu sein, dass die Kovarianz es erlauben würde, dass das Schloss kurzlebig ist, dass aber Kontravarianz und Invarianz nicht funktionieren würden, aber in der Praxis scheint es, dass nur 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, wodurch die Methode lock wie gewünscht funktioniert, wobei ein Borg mit einer invarianten Lebensdauer genommen wird. (Ein andere Lösung wäre lock nehmen &'a mut self zu haben, für ein veränderliches Referenz ist, wie bereits erwähnt, unveränderlich, das schlechter ist, aber, wie es unnötig Veränderlichkeit erfordert.)

Eine andere Sache, das erwähnen muss: der Inhalt Die 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 das ganze "konstruiere ein Unmovable Objekt und rufe lock darauf an" Teil von new_in ist völlig überflüssig. In ähnlicher Weise war das Einstellen des Inhalts der Zelle in lock eine Zeitverschwendung. (Beachten Sie, dass es wieder die Invarianz der 'a in Unmovable<'a> die new_in Arbeit macht, nicht die Tatsache, dass es sich um eine veränderbare Referenz.)

use std::marker; 

struct Unmovable<'a> { 
    lock: marker::InvariantLifetime<'a>, 
} 

impl<'a> Unmovable<'a> { 
    fn new() -> Unmovable<'a> { 
     Unmovable { 
      lock: marker::InvariantLifetime, 
     } 
    } 

    fn lock(&'a self) { } 

    fn new_in(_: &'a mut Option<Unmovable<'a>>) { } 
} 

fn main() { 
    let x = Unmovable::new(); 
    x.lock(); 

    // This is an error, as desired: 
    let z = x; 

    let mut y = None; 
    Unmovable::new_in(&mut y); 

    // Yay, this is an error too! 
    let z = y; 
} 
+0

Beantwortete alle meine Fragen! Außer dem, der nicht zu beantworten ist (warum kannst du dich mit * entweder * CovariantLifetime * oder ContravariantLifetime bewegen?), Was wie ein weiterer möglicher Compiler-Fehler aussieht. Vielen Dank! –

+1

Die angegebene Invarianz von '&' a mut T 'ist falsch: sie ist invariant gegenüber 'T' und kontravariant in Bezug auf''a. Es ist zulässig, ein '& 'ein mut T' zu einem '&' kleines mut T 'zu zwingen. – huon

+1

Auch "InvarianceLifetime" ist die Schnittmenge von 'ContravarianceLifetime' und' CovarianceLifetime', also verhält es sich in Bezug auf Einschränkungen wie beides. Jeder der Markierungstypen stellt tatsächlich das Entfernen der Funktionalität dar, anstatt sie hinzuzufügen: "Contra ..." ist nur * nicht * kovariant, "Co ..." ist einfach nicht kontravariant, und "In ..." ist weder kovariant noch kontravariant. – huon

1

Ein interessantes Problem! Hier ist mein Verständnis davon ...

Hier ist ein weiteres Beispiel, das nicht Cell nicht verwendet:

#![feature(core)] 

use std::marker::InvariantLifetime; 

struct Unmovable<'a> { //' 
    lock: Option<InvariantLifetime<'a>>, //' 
} 

impl<'a> Unmovable<'a> { 
    fn lock_it(&'a mut self) { //' 
     self.lock = Some(InvariantLifetime) 
    } 
} 

fn main() { 
    let mut u = Unmovable { lock: None }; 
    u.lock_it(); 
    let v = u; 
} 

(Playpen)

Der wichtige Trick dabei ist, dass die Struktur leihen muss sich . 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 verschieden von jeder anderen Art von borrow:

struct A(u32); 

fn main() { 
    let a = A(42); 
    let b = &a; 
    let c = a; 
} 

Die einzige Sache ist, dass Sie eine Möglichkeit zu lassen, die Struktur enthält seine eigene Referenz müssen, was nicht möglich ist, die Bauzeit zu tun. In meinem Beispiel wird Option verwendet, für das &mut self erforderlich ist. Im verknüpften Beispiel wird Cell verwendet, was eine Änderung des Innenraums und nur &self ermöglicht.

Beide Beispiele verwenden einen Lebenszeitmarker, da das System die Lebensdauer überwachen kann, ohne sich um eine bestimmte Instanz kümmern zu müssen.

Schauen wir uns Konstruktor:

fn new() -> Unmovable<'a> { //' 
    Unmovable { 
     lock: marker::ContravariantLifetime, 
     marker: marker::NoCopy 
    } 
} 

Hier wird die Lebensdauer in lock gesetzt wird vom Anrufer, gewählt und es endet als die normale Lebensdauer des Unmovable Struktur. Es gibt keinen eigenen Kredit.

nächsten Blick auf Ihre Sperre Methode Lassen Sie uns:

fn lock(&'a self) { 
} 

Hier weiß der Compiler, dass die Lebensdauer nicht verändern wird. Wenn wir es jedoch änderbar machen:

fn lock(&'a mut self) { 
} 

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!

+1

ich den Eindruck habe, dass Rust Lage sein sollten, Lebensdauern und leiht zu verfolgen über Anrufe, die streng auf Funktionssignaturen basieren, oder? Wenn wir also die 'Cell' um das' lock'-Feld halten, wird die Lebensdauer dieses Feldes * noch * vom Aufrufer bestimmt ... 'Cell' gewährt innere Veränderlichkeit, aber das ist im Funktionsprototyp von' nicht sichtbar Lock "in jedem Fall, so dass sollte nicht die Ergebnisse von der Borrow-Checker ändern, sollte es? Erkennt der Borrow-Checker irgendwie, dass eine Mutation möglich ist, wenn die Struktur eine Zelle enthält? –

+0

Eine ausgezeichnete Frage. Wenn hier keine weiteren sachkundigen Kommentare vorhanden sind, würde ich vorschlagen, sie als eine weitere Top-Level-Frage einzureichen. – Shepmaster

+1

Das sieht irgendwie wie ein Fehler für mich aus, an dieser Stelle. Ich kann noch nicht klar genug sagen, was vor sich geht, aber das Ändern des * Typs * des Kredits sollte nicht die * Tatsache * ändern, ob die Variable an dem Punkt ausgeliehen wird, an dem die Bewegung versucht wird. Das Ausleihen des Kredits sollte die Laufzeit des Kredits nicht verändert haben. Und die Bewegung sollte unmöglich sein, auch wenn der Wert nur unausweichlich ausgeliehen wird. Scheint ein bisschen fischig ... So oder so, vielen Dank für Ihre Hilfe! –

Verwandte Themen