rust encapsulation contravariance mutability interior-mutability

rust - ¿Cómo devuelvo una referencia a algo dentro de un RefCell sin romper la encapsulación?



encapsulation contravariance (3)

Tengo una estructura que tiene mutabilidad interna.

use std::cell::RefCell; struct MutableInterior { hide_me: i32, vec: Vec<i32>, } struct Foo { //although not used in this particular snippet, //the motivating problem uses interior mutability //via RefCell. interior: RefCell<MutableInterior>, } impl Foo { pub fn get_items(&self) -> &Vec<i32> { &self.interior.borrow().vec } } fn main() { let f = Foo { interior: RefCell::new(MutableInterior { vec: Vec::new(), hide_me: 2, }), }; let borrowed_f = &f; let items = borrowed_f.get_items(); }

Produce el error:

error[E0597]: borrowed value does not live long enough --> src/main.rs:16:10 | 16 | &self.interior.borrow().vec | ^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough 17 | } | - temporary value only lives until here | note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 15:5... --> src/main.rs:15:5 | 15 | / pub fn get_items(&self) -> &Vec<i32> { 16 | | &self.interior.borrow().vec 17 | | } | |_____^

El problema es que no puedo tener una función en Foo que devuelva un vec prestado, porque el vec prestado solo es válido durante la vida útil de la Ref , pero la Ref queda fuera de alcance inmediatamente.

Creo que el Ref debe quedarse because :

RefCell<T> utiliza la vida útil de Rust para implementar ''préstamos dinámicos'', un proceso mediante el cual uno puede reclamar acceso temporal, exclusivo y mutable al valor interno. Los RefCell<T> para RefCell<T> s se rastrean ''en tiempo de ejecución'', a diferencia de los tipos de referencia nativos de Rust que se rastrean completamente estáticamente, en tiempo de compilación. Debido a que los RefCell<T> son dinámicos, es posible intentar pedir prestado un valor que ya está prestado de manera mutable; cuando esto sucede, resulta en pánico de la tarea.

Ahora podría escribir una función como esta que devuelva todo el interior:

pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;

Sin embargo, esto expone potencialmente campos ( MutableInterior.hide_me en este ejemplo) que son detalles de implementación realmente privados para Foo .

Idealmente, solo quiero exponer el vec sí, potencialmente con un guardia para implementar el comportamiento dinámico de endeudamiento. Entonces las personas que llaman no tienen que averiguar acerca de hide_me .


En lugar de crear un tipo completamente nuevo, puede usar Ref::map (desde Rust 1.8). Esto tiene el mismo resultado que la respuesta existente de Levans :

use std::cell::Ref; impl Foo { pub fn get_items(&self) -> Ref<''_, Vec<i32>> { Ref::map(self.interior.borrow(), |mi| &mi.vec) } }

También puede usar nuevas características como el impl Trait para ocultar el Ref de la API:

use std::cell::Ref; use std::ops::Deref; impl Foo { pub fn get_items(&self) -> impl Deref<Target = Vec<i32>> + ''_ { Ref::map(self.interior.borrow(), |mi| &mi.vec) } }


Puede crear una nueva estructura similar a la protección Ref<''a,T> devuelta por RefCell::borrow() , para envolver esta Ref y evitar que quede fuera de alcance, de esta manera:

use std::cell::Ref; struct FooGuard<''a> { guard: Ref<''a, MutableInterior>, }

entonces, puede implementar el rasgo de Deref para que se pueda usar como si fuera un &Vec<i32> :

use std::ops::Deref; impl<''b> Deref for FooGuard<''b> { type Target = Vec<i32>; fn deref(&self) -> &Vec<i32> { &self.guard.vec } }

después de eso, actualice su método get_items() para devolver una instancia de FooGuard :

impl Foo { pub fn get_items(&self) -> FooGuard { FooGuard { guard: self.interior.borrow(), } } }

y Deref hace la magia:

fn main() { let f = Foo { interior: RefCell::new(MutableInterior { vec: Vec::new(), hide_me: 2, }), }; let borrowed_f = &f; let items = borrowed_f.get_items(); let v: &Vec<i32> = &items; }


Puedes envolver el Vec en un Rc .

use std::cell::RefCell; use std::rc::Rc; struct MutableInterior { hide_me: i32, vec: Rc<Vec<i32>>, } struct Foo { interior: RefCell<MutableInterior>, } impl Foo { pub fn get_items(&self) -> Rc<Vec<i32>> { self.interior.borrow().vec.clone() // clones the Rc, not the Vec } } fn main() { let f = Foo { interior: RefCell::new(MutableInterior { vec: Rc::new(Vec::new()), hide_me: 2, }), }; let borrowed_f = &f; let items = borrowed_f.get_items(); }

Cuando necesite mutar el Vec , use Rc::make_mut para obtener una referencia mutable al Vec . Si todavía hay otros Rc refieren al Vec , make_mut disociará el Rc de los otros Rc , clonará el Vec y se actualizará para referirse a ese nuevo Vec , luego le dará una referencia mutable. Esto asegura que el valor en los otros Rc s no cambie repentinamente (porque Rc por sí solo no proporciona mutabilidad interior).