ps4 - Encapsulando el estado secuencialmente inicializado con autorreferencias en Rust struct
rust traduccion (1)
Estoy tratando de definir una estructura que pueda actuar como un iterador para un Vec
que se mantiene dentro de un RefCell
:
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);
}
}
En comparación, vec_iter
se puede inicializar en línea en main()
siguiente manera (deliberadamente detallado):
// 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();
¿Hay alguna manera de definir una estructura que implemente Iterator
que contenga tanto el Ref
(para mantener vivo el préstamo RefCell
inmutable) como el Iter
(para mantener el estado del iterador para next()
, en lugar de extender mi propio iterador para Vec
o cualquier otro contenedor) , cuando el segundo se deriva de (y tiene una referencia obtenida de) el primero?
He intentado varios enfoques para implementar esto, y todos se encuentran con el comprobador de préstamos. Si pongo ambas piezas de estado como miembros de estructura vacía, como
struct HoldsVecInRefCellIter<''a> {
vec_ref: Ref<''a, Vec<i32>>,
vec_iter: Iter<''a, i32>,
}
entonces no puedo inicializar ambos campos a la vez con la HoldsVecInRefCellIter { ... }
(ver, por ejemplo, ¿Rust tiene sintaxis para inicializar un campo struct con un campo anterior? ). Si trato de derivar la inicialización secuencial con una estructura como
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
}
}
luego incurro en un auto-préstamo mutable de la estructura que impide devolverlo desde iter()
. Este auto-préstamo de una estructura también puede ocurrir si intentas almacenar una referencia a una parte de una estructura en la propia estructura ( ¿Por qué no puedo almacenar un valor y una referencia a ese valor en la misma estructura? ), Que evitaría mover instancias de forma segura de la estructura. En comparación, parece que una estructura como HoldsVecInRefCellIter
, si pudieras completar la inicialización, haría lo correcto cuando se moviera, ya que todas las referencias internas son a datos en otro lugar que sobreviven a esta estructura.
Hay trucos para evitar crear autorreferencias usando Rc
(vea ejemplos en https://internals.rust-lang.org/t/self-referencing-structs/418/3 ), pero no veo cómo podrían ser aplicado si desea almacenar una estructura Iterator
existente que se implementa para contener una referencia directa al contenedor subyacente, no un Rc
.
Como un novato de Rust procedente de C ++, se siente como un problema que surgiría a menudo ("Tengo una lógica de inicialización de estado compleja en un bloque de código, y quiero abstraer esa lógica y mantener el estado resultante en una estructura para utilizar").
Pregunta relacionada: Volver iterador de un Vec en un RefCell
Tendremos que hacer trampa y mentir sobre vidas.
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()
}
}
Esto solo funciona porque el Iter
no se invalida moviendo el Ref
, ya que el Ref
señala el Vec
, y el Iter
apunta al almacenamiento del Vec
, no en el Ref
.
Sin embargo, esto también le permite mover vec_iter
fuera de HoldsVecInRefCellIter
; si extrae vec_iter
y vec_ref
, se vec_ref
el préstamo y el Iter
podría invalidarse sin que Rust presente un error de compilación ( ''a
es la vida útil del RefCell
). Con la encapsulación adecuada, puede mantener los contenidos de la estructura en privado y evitar que los usuarios realicen esta operación insegura.
Por cierto, podríamos definir el iterador para devolver referencias:
impl<''a> Iterator for HoldsVecInRefCellIter<''a> {
type Item = &''a i32;
fn next(&mut self) -> Option<Self::Item> {
self.vec_iter.next()
}
}