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.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Jake
  • 245
  • 2
  • 10
  • How is the data from LibraryStruct made available to A? I presume you cannot add methods to the LibraryStruct or change its data structure? – Peter Hall Sep 01 '18 at 20:51
  • a method returns clone of the rc. Note I'm writing the "Library" code and want to prevent the issue if possible. – Jake Sep 01 '18 at 21:00

1 Answers1

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() };
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • The library struct / data is modifiable, would this still be the best solution? – Jake Sep 01 '18 at 21:13
  • 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. – Peter Hall Sep 01 '18 at 21:14
  • 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? – Jake Sep 01 '18 at 21:18
  • 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. – Peter Hall Sep 01 '18 at 21:20
  • 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. – Jake Sep 01 '18 at 21:35
  • Your updated solution could work as Rc> alternative. Thanks – Jake Sep 01 '18 at 21:38