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. LosRefCell<T>
paraRefCell<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 losRefCell<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).