Как вернуть ссылку на что-то внутри RefCell без нарушения инкапсуляции?
У меня есть структура, которая имеет внутреннюю изменчивость.
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();
}
Производит ошибку:
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 | | }
| |_____^
Проблема в том, что я не могу иметь функцию на Foo
которая возвращает заимствованный vec
, потому что заимствованный vec
действителен только для времени жизни Ref
, но Ref
немедленно выходит из области видимости.
Я думаю, что Ref
должен стоять, потому что:
RefCell<T>
использует времена жизни Rust для реализации "динамического заимствования", процесс, при котором можно требовать временный, исключительный, изменяемый доступ к внутреннему значению. RefCell<T>
для RefCell<T>
отслеживаются "во время выполнения", в отличие от стандартных ссылочных типов Rust, которые полностью отслеживаются статически во время компиляции. Так как RefCell<T>
занимает динамические данные, можно попытаться заимствовать значение, которое уже было заимствовано; когда это происходит, это приводит к панике задач.
Теперь я мог бы написать такую функцию, которая возвращает весь интерьер:
pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;
Однако это потенциально предоставляет поля (MutableInterior.hide_me
в этом примере), которые действительно являются MutableInterior.hide_me
реализации для Foo
.
В идеале я просто хочу разоблачить сам vec
, потенциально с защитой для реализации поведения динамического заимствования. Затем вызывающим абонентам не нужно узнавать о hide_me
.
Ответы
Ответ 1
Вы можете создать новую структуру, подобную RefCell::borrow()
Ref<'a,T>
возвращенной RefCell::borrow()
, чтобы обернуть этот Ref
и избежать его выхода из области видимости:
use std::cell::Ref;
struct FooGuard<'a> {
guard: Ref<'a, MutableInterior>,
}
то вы можете реализовать для Deref
черту Deref
, чтобы ее можно было использовать, как если бы она была &Vec<i32>
:
use std::ops::Deref;
impl<'b> Deref for FooGuard<'b> {
type Target = Vec<i32>;
fn deref(&self) -> &Vec<i32> {
&self.guard.vec
}
}
после этого обновите свой get_items()
чтобы вернуть экземпляр FooGuard
:
impl Foo {
pub fn get_items(&self) -> FooGuard {
FooGuard {
guard: self.interior.borrow(),
}
}
}
и Deref
делает магию:
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;
}
Ответ 2
Вы можете обернуть Vec
в 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();
}
Когда вам необходимо Rc::make_mut
Vec
, используйте Rc::make_mut
для получения изменчивой ссылки на Vec
. Если есть еще Rc
ссылающийся на Vec
, make_mut
будет отделять Rc
от других Rc
s, клонирует Vec
и сам обновляется, чтобы ссылаться на этот новый Vec
, а затем дать вам изменяемую ссылку на него. Это гарантирует, что значение в другом Rc
не изменится внезапно (поскольку Rc
сам по себе не обеспечивает внутреннюю изменчивость).
Ответ 3
Вместо создания совершенно нового типа вы можете использовать Ref::map
(начиная с Rust 1.8). Это имеет тот же результат, что и ответ Леванса:
use std::cell::Ref;
impl Foo {
pub fn get_items(&self) -> Ref<'_, Vec<i32>> {
Ref::map(self.interior.borrow(), |mi| &mi.vec)
}
}
Вы также можете использовать новые функции, такие как impl Trait
чтобы скрыть Ref
от 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)
}
}