如何在不破坏封装的情况下返回对RefCell内部内容的引用?

Dre*_*rew 21 encapsulation contravariance mutability rust interior-mutability

我有一个内部可变性的结构.

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();
}
Run Code Online (Sandbox Code Playgroud)

产生错误:

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 | |     }
   | |_____^
Run Code Online (Sandbox Code Playgroud)

问题是我不能有一个Foo返回借用的函数vec,因为借用vec只在生命周期内有效Ref,但是它Ref会立即超出范围.

我认为Ref必须坚持下去,因为:

RefCell<T>使用Rust的生命周期来实现"动态借用",这是一个可以声称对内在值进行临时,独占,可变访问的过程.对于RefCell<T>s的借位是"在运行时"跟踪的,不同于Rust的本机引用类型,它们在编译时被完全静态跟踪.因为RefCell<T>借用是动态的,所以可以尝试借用已经可以借来的价值; 当发生这种情况时,会导致任务恐慌.

现在我可以编写一个这样的函数来返回整个内部:

pub fn get_mutable_interior(&self) -> std::cell::Ref<MutableInterior>;
Run Code Online (Sandbox Code Playgroud)

但是,这可能会暴露MutableInterior.hide_me实际私有实现细节的字段(在此示例中)Foo.

理想情况下,我只想暴露vec自己,可能会有一个守卫来实现动态借用行为.然后呼叫者不必了解hide_me.

Lev*_*ans 19

你可以创建一个类似于Ref<'a,T>返回的守护的新结构RefCell::borrow(),以包装它Ref并避免它超出范围,如下所示:

use std::cell::Ref;

struct FooGuard<'a> {
    guard: Ref<'a, MutableInterior>,
}
Run Code Online (Sandbox Code Playgroud)

然后,你可以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
    }
}
Run Code Online (Sandbox Code Playgroud)

之后,更新您的get_items()方法以返回FooGuard实例:

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

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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这是唯一/惯用的方法吗?似乎有点麻烦......虽然我想而不是 getItems() 方法,你可以直接借用块中的内部结构,然后它会超出范围(或其他东西......) (3认同)
  • @Norcalli在`RefCell`的特定情况下,当引用超出范围时(这就是`Ref`的析构函数),需要通知对象.在这里,我们需要保留这种行为(OP的错误是由于`Ref`实例被过早丢弃),因此将其封装起来. (2认同)

She*_*ter 12

您可以使用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)
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用新功能,例如从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)
    }
}
Run Code Online (Sandbox Code Playgroud)


Fra*_*gné 5

您可以将 包装VecRc.

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();
}
Run Code Online (Sandbox Code Playgroud)

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