2016-12-08 3 views
1

Ich versuche, einen Container für Objekte des Typs T zu schreiben, die Zugriff auf Referenzen &T zu den gespeicherten Objekten bietet (ich möchte vermeiden, Kopien zu erstellen). Da der Behälter nur während seiner Lebensdauer wächst, sollte die Lebensdauer der zurückgegebenen Referenzen &T die gleiche wie für den Container sein.Gibt es eine idiomatische Möglichkeit, Referenzen auf Elemente eines ständig wachsenden Containers zu halten?

Der nächste, den ich bis jetzt bekommen habe, war, intern Box<T> Objekte in meinem Container zu verwenden und Box<T>.as_ref() zu verwenden, um Verweise auf diese Objekte zurückzugeben. Dann aber ich laufe in das gleiche Problem wie in diesem minimal Beispiel:

fn main() { 
    let mut v = vec![Box::new(1)]; // line 1 
    let x = v[0].as_ref();   // line 2: immutable borrow occurs here 
    println!("x = {:?}", x);  // line 3 
    v.push(Box::new(2));   // line 4: mutable borrow occurs here -> error 
    println!("x = {:?}", x);  // line 5 
} 

Ich verstehe, dass es unvernünftig wäre x in Zeile 5 zu verwenden, wenn es von v während der der wandelbaren borrow gelöscht worden war. Aber das ist hier nicht der Fall, und es wird nie für meinen Container sein. Wenn es keinen sicheren Weg gibt, dies in Rust auszudrücken, wie könnte ich das Beispiel "reparieren" (ohne zu kopieren x)?

Antwort

1

Leider gibt es nichts „ready to use“ für Ihre usecase, aber Sie können einen Wrapper um Vec schreiben das macht unsicher Zeug Sie die Eigenschaften, die Sie benötigen, während die Garantien zu halten, dass Rust

struct BoxVec<T>(Vec<Box<T>>); 

impl<T> BoxVec<T> { 
    pub fn new() -> Self { BoxVec(Vec::new()) } 
    pub fn push<'a>(&'a mut self, t: T) -> &'a mut T { 
     let mut b = Box::new(t); 
     let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) }; 
     self.0.push(b); 
     t 
    } 
    pub fn pusher<'a>(&'a mut self) -> BoxVecPusher<'a, T> { 
     BoxVecPusher(self) 
    } 
} 

struct BoxVecPusher<'a, T: 'a>(&'a mut BoxVec<T>); 

impl<'a, T> BoxVecPusher<'a, T> { 
    fn push<'b>(&'b mut self, t: T) -> &'a mut T { 
     let mut b = Box::new(t); 
     let t: &'a mut T = unsafe { std::mem::transmute::<&mut T, &'a mut T>(&mut b) }; 
     (self.0).0.push(b); 
     t 
    } 
} 

die Garantien muss ich gewählt ist, dass man nicht Index in die Pusher, aber Sie erhalten einen veränderlichen Verweis auf das neu verschobene Objekt. Sobald Sie den Drücker loslassen, können Sie zur Indexierung zurückkehren.

Ein Beispiel für die Verwendung ist

let mut v = BoxVec::new(); 
v.push(1); 
let x = v[0]; 
let mut p = v.pusher(); 
let i = p.push(2); 
p.push(3); // works now 
let y = v[0]; // denied by borrow checker 

Full example in the playground

2

Das Problem ist, dass Ihr Vec möglicherweise nicht genügend Arbeitsspeicher hat. Wenn das passiert, muss es einen neuen (größeren) Speicherblock reservieren, alle alten Daten kopieren und die alte Speicherbelegung löschen. Zu diesem Zeitpunkt würden alle Referenzen in den alten Container ungültig werden.

Sie könnten einen Entwurf rope (eine Vec von Vec), wo einst eine innere vec voll ist, können Sie ein neues gerade erstellen, also nie Zeiger nur durch Drücken ungültig zu machen.

Das würde einige unsicheren Code und sehr sorgfältige Analyse der Borrowing-Regeln erfordern, die Sie mithalten müssen.

+0

ich die Verweise auf die Elemente des Vec entkräften mögliche Umverteilung bewusst bin. Deshalb habe ich Vec verwendet >: die Verweise auf die Box sind instabil, aber die Hinweise auf die T sind stabil, nicht wahr? Immerhin benötigt Vec > Box nicht, um klonbar zu sein, so dass es die Referenzen auf das T nicht ungültig machen kann. – chs

+0

An diesem Punkt, was Sie wirklich wollen, ist 'Rc ', denn das ist buchstäblich die Definition einer gemeinsamen Besitz Referenz in Rust –

+0

Die Vec ist der (einzige) Besitzer des Zeigers, so Referenzzählung ist nicht das, was ich will . Es ist vollkommen in Ordnung, wenn die Referenzen ungültig werden, sobald die Lebensdauer des besitzenden Vec endet. (Ich stimme zu, dass das Referenzzählen das gesamte Besitzproblem vermeiden würde. Aber ich möchte, dass die Referenzen aus Leistungsgründen so leicht wie möglich sind.) – chs

5

Wie @kers Antwort sagt, nur weil Sie nur einen Container wachsen, bedeutet das nicht, dass Referenzen gültig bleiben, da der Speicher neu zugewiesen werden kann.

Eine andere Lösung, wenn Sie nur den Container wächst, ist nur Indizes speichern, anstatt von Referenzen:

fn main() { 
    let mut v = vec!["Foo"]; // line 1 
    let x = 0;     // line 2: just store an index. 
    println!("x = {:?}", v[x]); // Use the index as needed 
    v.push("bar");    // line 4: No problem, there are no references. 
    println!("x = {:?}", v[x]); // line 5: use the index again. 
} 
+0

Die Neuzuweisung des Vec-Puffers sollte hier kein Problem sein, da der Vec die Box verschiebt, nicht die T - Also sollten Verweise auf T stabil bleiben. Die Indexlösung ist in meinem Fall ziemlich suboptimal, weil das Identifizieren des Elements den Index plus einen Verweis auf den Container erfordert. – chs

+0

Wenn du im sicheren Rust bleiben willst, wenn du einen Hinweis auf '* v [0]' hast, dann leihst du 'v' auch, da Rust sonst 'v [0] = nicht fangen kann Box :: new ("baz") 'was die Referenz ungültig machen würde. Ich stimme @ker zu, dass 'Rc ' hier die beste Antwort sein kann. –

Verwandte Themen