提问人:Drew 提问时间:4/2/2015 最后编辑:Peter HallDrew 更新时间:8/1/2023 访问量:13465
如何在不破坏封装的情况下返回对 RefCell 中某些内容的引用?
How do I return a reference to something inside a RefCell without breaking encapsulation?
问:
我有一个具有内部可变性的结构。
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 的生命周期来实现“动态借用”,在这个过程中,人们可以声称对内部值的临时、排他、可变的访问。借用的 s 是在“运行时”跟踪的,不像 Rust 的原生引用类型在编译时完全静态跟踪。由于借入是动态的,因此可以尝试借入已经可变借入的值;发生这种情况时,会导致任务崩溃。RefCell<T>
RefCell<T>
现在我可以编写一个这样的函数来返回整个内部:
pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;
但是,这可能会向 公开字段(在本例中)实际上是私有实现细节的字段。MutableInterior.hide_me
Foo
理想情况下,我只想公开它本身,可能带有一个守卫来实现动态借用行为。这样来电者就不必了解 .vec
hide_me
答:
您可以创建一个类似于 返回的防护的新结构,以便包装它并避免它超出范围,如下所示:Ref<'a,T>
RefCell::borrow()
Ref
use std::cell::Ref;
struct FooGuard<'a> {
guard: Ref<'a, MutableInterior>,
}
然后,你可以为它实现 trait,这样它就可以被当作一个: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;
}
评论
RefCell
Ref
Ref
你可以使用 Ref::map
(从 Rust 1.8 开始)而不是创建一个全新的类型。这与 Levans 现有的答案具有相同的结果:
use std::cell::Ref;
impl Foo {
pub fn get_items(&self) -> Ref<'_, Vec<i32>> {
Ref::map(self.interior.borrow(), |mi| &mi.vec)
}
}
您还可以使用新功能,例如从 API 中隐藏:impl Trait
Ref
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)
}
}
评论
map
Mutex
cell.rs
mutex.rs
MutexGuard
您可以将 .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
获取对 的可变引用。如果还有其他 s 引用 ,将与其他 s 分离,克隆 并更新自身以引用该新 ,然后给你一个可变的引用。这确保了其他 s 中的值不会突然更改(因为其本身不提供内部可变性)。Vec
Vec
Rc
Vec
make_mut
Rc
Rc
Vec
Vec
Rc
Rc
Ref<X>
是通常的答案。
但是,有时,例如在设计特征
时,需要一致的接口。例如,具有以下特征的特征:
fn get_x(&self) -> &X
将排除结构存储在 中,而:x
RefCell<X>
fn get_x(&self) -> Ref<X>
将排除结构存储为普通 .x
X
可以用来解决这个问题的模式是采用一种对数据进行操作的方法。
fn with_x(&self, fun : &dyn FnOnce(&X));
它适用于使用 and(或任何其他组合)的两个类:X
RefCell<X>
fun(&self.x);
// or
fun(&self.x.borrow());
如果你真的必须返回对数据的引用(因为在外部 crate 中需要一些接口或类似接口),你可以使用 Rc::leak 来做到这一点,它目前需要 ,直到它稳定下来,你可以用这个小函数来帮助自己:
nightly
use std::cell::Ref;
fn leak_ref<'a, T>(orig: Ref<'a, T>) -> &'a T {
Box::leak(Box::new(orig))
}
与原文相同的免责声明适用于:leak
底层永远不会再被可变地借用,并且总是看起来已经不可变地被借用了。泄漏超过恒定数量的引用不是一个好主意。如果总共只发生了少量泄漏,则可以再次不可变地借用。
RefCell
RefCell
评论