是否有替代方案或方法来限制X的可变性,同时仍使用Rc<RefCell<X>>?

7

例如,给出此代码

use std::rc::Rc;
use std::cell::RefCell;

// Don't want to copy for performance reasons
struct LibraryData {
    // Fields ...
}

// Creates and mutates data field in methods
struct LibraryStruct {
    // Only LibraryStruct should have mutable access to this
    data: Rc<RefCell<LibraryData>>
}

impl LibraryStruct {
    pub fn data(&self) -> Rc<RefCell<LibraryData>> {
        self.data.clone()
    }
}

// Receives data field from LibraryStruct.data()
struct A {
    data: Rc<RefCell<LibraryData>>
}

impl A {
    pub fn do_something(&self) {
        // Do something with self.data immutably

        // I want to prevent this because it can break LibraryStruct
        // Only LibraryStruct should have mutable access 
        let data = self.data.borrow_mut();
        // Manipulate data
    }
}

我该如何防止LibraryDataLibraryStruct之外被改变?LibraryStruct应该是唯一能够在其方法中改变data的对象。使用Rc<RefCell<LibraryData>>是否可行,还是有其他替代方案?需要注意的是,我正在编写“library”代码,因此可以对其进行更改。


LibraryStruct中的数据如何提供给A使用?我想你不能添加LibraryStruct的方法或更改其数据结构,对吗? - Peter Hall
一个方法返回rc的克隆。请注意,我正在编写“库”代码,并希望尽可能避免问题。 - Jake
1个回答

8
如果你共享一个 RefCell,那么它总是可能被改变 - 这本质上就是它的全部意义。鉴于你能够更改 LibraryStruct 的实现,你可以确保 data 不是公共的,并通过 getter 方法控制其向用户暴露的方式:
pub struct LibraryStruct {
    // note: not pub
    data: Rc<RefCell<LibraryData>>
}

impl LibraryStruct {
    // could also have returned `Ref<'a, LibraryData> but this hides your 
    // implementation better
    pub fn data<'a>(&'a self) -> impl Deref<Target = LibraryData> + 'a {
        self.data.borrow()
    }
}

在您的其他结构体中,您可以简单地将其视为引用来处理:
pub struct A<'a> {
    data: &'a LibraryData,
}

impl<'a> A<'a> {
    pub fn do_something(&self) {
        // self.data is only available immutably here because it's just a reference
    }
}

fn main() { 
    let ld = LibraryData {};
    let ls = LibraryStruct { data: Rc::new(RefCell::new(ld)) };

    let a = A { data: &ls.data() };
}

如果您需要更长时间地保持引用,而在库代码中原始的RefCell需要进行可变借用,那么您需要创建一个自定义包装器来管理。可能有一个标准库类型可以实现这个功能,但我不知道它是什么,您可以根据自己的需要轻松地创造一个:

// Wrapper to manage a RC<RefCell> and make it immutably borrowable
pub struct ReadOnly<T> {
    // not public
    inner: Rc<RefCell<T>>,
}

impl<T> ReadOnly<T> {
    pub fn borrow<'a>(&'a self) -> impl Deref<Target = T> + 'a {
        self.inner.borrow()
    }
}

现在在你的库代码中返回这个:
impl LibraryStruct {
    pub fn data<'a>(&'a self) -> ReadOnly<LibraryData> {
        ReadOnly { inner: self.data.clone() }
    }
}

当您使用它时,内部的RefCell不可直接访问,数据仅以不可变方式借用:

pub struct A {
    data: ReadOnly<LibraryData>,
}

impl A {
    pub fn do_something(&self) {
        //  data is immutable here
        let data = self.data.borrow();
    }
}

fn main() { 
    let ld = LibraryData {};
    let ls = LibraryStruct { data: Rc::new(RefCell::new(ld)) };

    let a = A { data: ls.data() };
}

这个库的结构/数据是可修改的,这仍然是最好的解决方案吗? - Jake
如果您可以更改库,并且不希望在该代码之外进行编辑,则应将其保持私有并防止修改。我只是更新答案以反映这一点。 - Peter Hall
我认为第一种解决方案行不通,因为我不能保持一个活动的Ref坐在那里,因为当LibraryStruct修改数据时会导致恐慌。 - Jake
如果您需要借用更长的时间,以便在此期间可以进行变异,则此方法将不起作用。 您可能需要编写一些自定义内容,只能进行不可变的借用。但是,由于每个包装器类型都不同,您也会遇到Rc包装器的问题。 - Peter Hall
我基本上想提供指向LibraryStruct.data的指针,这些指针只是不可变的。我可能需要重新设计整个部分的工作方式,似乎与rust的方式相悖。 - Jake
你更新后的解决方案可以作为 Rc<RefCell<X>> 的替代品。谢谢。 - Jake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接