2016-09-12 15 views
11

próbuję zdefiniować struct, który może działać jako iterator dla Vec, że odbywa się w ramach RefCell:Encapsulating kolejno zainicjowany stan z własnym odniesień w Rust struct

use std::slice::Iter; 
use std::cell::Ref; 
use std::cell::RefCell; 

struct HoldsVecInRefCell { 
    vec_in_refcell: RefCell<Vec<i32>>, 
} 

// TODO: struct HoldsVecInRefCellIter implementing Iterator ... 

impl HoldsVecInRefCell { 
    fn new() -> HoldsVecInRefCell { 
     HoldsVecInRefCell { vec_in_refcell: RefCell::new(Vec::new()) } 
    } 

    fn add_int(&self, i: i32) { 
     self.vec_in_refcell.borrow_mut().push(i); 
    } 

    fn iter(&self) -> HoldsVecInRefCellIter { 
     // TODO ... 
    } 
} 

fn main() { 
    let holds_vec = HoldsVecInRefCell::new(); 
    holds_vec.add_int(1); 
    holds_vec.add_int(2); 
    holds_vec.add_int(3); 

    let mut vec_iter = holds_vec.iter(); // Under the hood: run-time borrow check 

    for i in vec_iter { 
     println!("{}", i); 
    } 
} 

porównania, vec_iter can być inicjowane w linii w main() następująco (celowo gadatliwy):

// Elided: lifetime parameter of Ref 
let vec_ref: Ref<Vec<i32>> = holds_vec.vec_in_refcell.borrow(); 
// Elided: lifetime parameter of Iter 
let mut vec_iter: Iter<i32> = vec_ref.iter(); 

Czy istnieje jakiś sposób, aby zdefiniować struct wykonawczych Iterator, która posiada zarówno Ref (aby utrzymać niezmienny numer RefCell pożyczać przy życiu) i Iter (aby zachować stan iteratora dla next(), zamiast toczyć własny iterator dla Vec lub dowolnego innego kontenera), gdy drugi pochodzi z (i zawiera referencję uzyskaną od) pierwszy?

Próbowałem kilku podejść do realizacji tego, a wszystkie działają wbrew sprawdzeniu pożyczek. Jeśli mogę umieścić zarówno państwowych jak kawałki gołych struct członków, jak

struct HoldsVecInRefCellIter<'a> { 
    vec_ref: Ref<'a, Vec<i32>>, 
    vec_iter: Iter<'a, i32>, 
} 

potem nie mogę zainicjować oba pola na raz HoldsVecInRefCellIter { ... } składni (patrz np Does Rust have syntax for initializing a struct field with an earlier field?). Gdy próbuję manewrowania sekwencyjną inicjalizacji z struct jak

struct HoldsVecInRefCellIter<'a> { 
    vec_ref: Ref<'a, Vec<i32>>, 
    vec_iter: Option<Iter<'a, i32>>, 
} 

// ... 

impl HoldsVecInRefCell { 
    // ... 

    fn iter(&self) -> HoldsVecInRefCellIter { 
     let mut new_iter = HoldsVecInRefCellIter { vec_ref: self.vec_in_refcell.borrow(), vec_iter: None }; 
     new_iter.vec_iter = new_iter.vec_ref.iter(); 
     new_iter 
    } 
} 

potem ponieść zmienny siebie pożyczyć struct, który zapobiega jego zwrot od iter(). To samo-pożyczanie struktury może się również zdarzyć, jeśli spróbujesz zapisać odniesienie do jednej części struktury w samej strukturze (Why can't I store a value and a reference to that value in the same struct?), co uniemożliwiłoby bezpieczne przenoszenie instancji struktury. Dla porównania wygląda na to, że struktura taka jak HoldsVecInRefCellIter, jeśli mógłbyś zakończyć inicjalizację, zrobiłaby właściwą rzecz podczas przenoszenia, ponieważ wszystkie odniesienia wewnętrznie odnoszą się do danych w innym miejscu niż ta struktura.

Istnieją sztuczki, aby uniknąć tworzenia self-referencje użyciu Rc (patrz przykłady na https://internals.rust-lang.org/t/self-referencing-structs/418/3), ale nie widzę w jaki sposób mogłyby one być stosowane, jeżeli chcesz zachować istniejący Iterator struct który jest realizowany na zorganizowanie bezpośredniego odniesienia do podstawowego pojemnika, a nie do Rc.

Jako początkujący Rust pochodzący z C++, wydaje się, że jest to problem, który często pojawiał się ("Mam pewną logikę inicjalizacji stanu złożonego w bloku kodu i chcę odjąć tę logikę i utrzymać stan wynikowy w strukturze do użycia ").

pokrewne Pytanie:Returning iterator of a Vec in a RefCell

+0

Complex inicjalizacji jest powszechne - to zazwyczaj rozwiązane przez wzór Builder; to nie problem tutaj. Iterator plasterka oczekuje, że będzie w stanie utrzymać odniesienie do plasterka przez cały czas jego istnienia; w rzeczywistości [nie można napisać iteratora, który zawiera odniesienia do siebie] (http://stackoverflow.com/questions/25702909/can-i-write-an-iterator-that-yields-a-reference-into-self). – Shepmaster

+0

Możesz dodać metodę 'fn zapożycz (& self) -> Ref >, którą możesz zapisać, a następnie wywołać' iter' na, lub możesz zaakceptować zamknięcie, które jest dostarczane jako iterator: 'fn iter )> (& self, f: F) ' – Shepmaster

+0

Aby wyjaśnić: moim zamiarem było, aby iterator zwracał wartości, a nie referencje (' i32' w przykładowym kodzie). Moim wyzwaniem było zainicjowanie struktury iteracyjnej, która może wytwarzać wartości przez przechowywanie "Iteratora" dla podstawowego kontenera. W tym przykładzie 'HoldsVecInRefCellIter' może pomieścić tylko' Ref > 'i indeks w wektorze, ale dla innych' Ref > 'Wyobrażam sobie, że chcesz" Iterator "kontenera. –

Odpowiedz

6

Będziemy musieli oszukiwać i kłamać na temat życia.

use std::mem; 

struct HoldsVecInRefCellIter<'a> { 
    vec_ref: Ref<'a, Vec<i32>>, 
    vec_iter: Iter<'a, i32>, // 'a is a lie! 
} 

impl HoldsVecInRefCell { 
    fn iter(&self) -> HoldsVecInRefCellIter { 
     unsafe { 
      let vec_ref = self.vec_in_refcell.borrow(); 
      // transmute changes the lifetime parameter on the Iter 
      let vec_iter = mem::transmute(vec_ref.iter()); 
      HoldsVecInRefCellIter { vec_ref: vec_ref, vec_iter: vec_iter } 
     } 
    } 
} 

impl<'a> Iterator for HoldsVecInRefCellIter<'a> { 
    type Item = i32; 

    fn next(&mut self) -> Option<Self::Item> { 
     self.vec_iter.next().cloned() 
    } 
} 

Zadziała tylko ponieważ Iter nie podważa przesuwając Ref, jak Ref punktów do Vec i Iter punkty Przechowywanie Vec „s, a nie za sam Ref.

Umożliwia to również przeniesienie vec_iter z HoldsVecInRefCellIter; jeśli wyodrębnisz vec_iter i zrzucisz vec_ref, zapożyczenie zostanie zwolnione, a Iter może zostać unieważnione bez Rusta podając błąd kompilatora ('a to czas życia RefCell). Dzięki odpowiedniej enkapsulacji możesz zachować zawartość struct w sposób prywatny i unikać wykonywania tej niebezpiecznej operacji.

Nawiasem mówiąc, moglibyśmy równie dobrze zdefiniować iterator powrotu nazwy:

impl<'a> Iterator for HoldsVecInRefCellIter<'a> { 
    type Item = &'a i32; 

    fn next(&mut self) -> Option<Self::Item> { 
     self.vec_iter.next() 
    } 
}