如何在不破坏封装的情况下 return 引用 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上有一个returns借来的vec的函数,因为借来的vec只在生命周期内有效Ref,但 Ref 立即超出范围。

我认为 Ref 必须坚持 because:

RefCell<T> uses Rust's lifetimes to implement 'dynamic borrowing', a process whereby one can claim temporary, exclusive, mutable access to the inner value. Borrows for RefCell<T>s are tracked 'at runtime', unlike Rust's native reference types which are entirely tracked statically, at compile time. Because RefCell<T> borrows are dynamic it is possible to attempt to borrow a value that is already mutably borrowed; when this happens it results in task panic.

现在我可以编写这样的函数 returns 整个内部:

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

然而,这可能会向 Foo.

暴露真正私有实现细节的字段(本例中的 MutableInterior.hide_me

理想情况下,我只是想公开 vec 本身,可能有一个守卫来实现动态借用行为。那么来电者就不必去了解hide_me.

您可以创建一个类似于 RefCell::borrow() 编写的 Ref<'a,T> 守卫 return 的新结构,以包装此 Ref 并避免将其外出作用域,像这样:

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}

然后,你可以为它实现 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() 方法更新为 return 一个 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;
}

您可以使用 Ref::map (since Rust 1.8). This has the same result as :

而不是创建全新的类型
use std::cell::Ref;

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

您还可以使用 impl Trait 等新功能从 API 中隐藏 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)
    }
}

您可以将 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();
}

当您需要改变 Vec 时,请使用 Rc::make_mut 获取对 Vec 的可变引用。如果还有其他 Rc 引用 Vecmake_mut 会将 Rc 与其他 Rc 分离,克隆 Vec并更新自身以引用新的 Vec,然后为您提供对它的可变引用。这确保了其他 Rc 中的值不会突然改变(因为 Rc 本身不提供内部可变性)。