Is there an alternative or way to have Rc<RefCell<X>> that restricts mutability of X?
Asked Answered
S

1

7

For example given this code:

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

How can I prevent LibraryData from being mutated outside of LibraryStruct? LibraryStruct should be the only one able to mutate data in its methods. Is this possible with Rc<RefCell<LibraryData>> or is there an alternative? Note I'm writing the "library" code so I can change it.

Stater answered 1/9, 2018 at 20:18 Comment(2)
How is the data from LibraryStruct made available to A? I presume you cannot add methods to the LibraryStruct or change its data structure?Vizierate
a method returns clone of the rc. Note I'm writing the "Library" code and want to prevent the issue if possible.Stater
V
8

If you share a RefCell then it will always be possible to mutate it - that's essentially the whole point of it. Given that you are able to change the implementation of LibraryStruct, you can make sure that data is not public, and control how it is exposed to its users through a getter method:

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

In your other struct, you can keep things simple, by just treating it as a reference:

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() };
}

If you need to hold the reference for longer, during which time the original RefCell needs to be mutably borrowed in the library code, then you need to make a custom wrapper which can manage that. It's possible that there's a standard library type for this, but I don't know of it and it's easy to make something specifically for your use case:

// 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()
    }
}

Now return this in your library code:

impl LibraryStruct {
    pub fn data<'a>(&'a self) -> ReadOnly<LibraryData> {
        ReadOnly { inner: self.data.clone() }
    }
}

And when you use it, the inner RefCell will not be directly accessible and the data is only available to borrow immutably:

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() };
}
Vizierate answered 1/9, 2018 at 21:7 Comment(6)
The library struct / data is modifiable, would this still be the best solution?Stater
If you can change the library, and you don't want it edited outside of that code, then you should keep it private and prevent modifications. I'm just updating the answer to reflect that.Vizierate
I believe the first solution won't work because I can't keep an active Ref sitting around because it will cause a panic when LibraryStruct modifies data?Stater
If you need the borrow to last longer, so it can be mutated in the meantime, then this won't work. You may need to write something custom that can only borrow immutably. But then you'll also run into problems with the Rc wrapper because the type will be different for each.Vizierate
I basically want to give out pointers that point to the LibraryStruct.data that are only immutable. I probably need to redesign how this whole part works, seems to be going against the rust way.Stater
Your updated solution could work as Rc<RefCell<X>> alternative. ThanksStater

© 2022 - 2024 — McMap. All rights reserved.