Есть ли альтернатива или способ иметь Rc <RefCell <X>>, который ограничивает изменчивость X? - PullRequest
0 голосов
/ 01 сентября 2018

Например, учитывая этот код :

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
    }
}

Как я могу предотвратить мутацию LibraryData за пределами LibraryStruct? LibraryStruct должен быть единственным, способным мутировать data в своих методах. Возможно ли это с Rc<RefCell<LibraryData>> или есть альтернатива? Обратите внимание, что я пишу код "библиотеки", чтобы я мог его изменить

1 Ответ

0 голосов
/ 02 сентября 2018

Если вы поделитесь 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() };
}
...