What interior mutability wrapper to use

Hi, I'm trying to understand the concepts of interior mutability, but I'm finding a hard time actually implementing it! I do not know if this is possible to achieve this without such pattern, but i would love your insights!

The problem is this:
I have a struct with a HashMap of vectors with trait objects called entities:

entities: HashMap<usize, Vec<Rc<dyn Component>>>

...and a field for a HashMap with mutable refrences to the component object who is owned by the entity vectors:

subscriptions: HashMap<UnrelatedEnum, Vec<&'a mut Rc<dyn Component>>>

I want inserting methods for both of these HashMaps like:

pub fn add_component<T: Component + 'static>(
    &mut self, 
    entity: &usize, 
    component: T
) {
    self.entities[entity].push(Rc::new(component));
}

I do not know if Rc is the correct pattern to use in this case, and this is my question,
How would i implement a function for adding subscriptions, and what interior mutability pattern should i use if any?

The refrence in the subscription field is mutable as i want to call a mut trait method on the component. How would i call this function?

This is my test case:

struct TestComponent(u32);
impl Component for TestComponent {
    fn on(&mut self, /*...*/) {
        println!("...");
    }
}

fn main() {
    let mut world = World::new(); // The struct this article is about

    let test = TestComponent(4);
    // ...
    world.add_component(&entity, test);
    world.add_subscription(UnrelatedEnum::Something, test); // i need help here!
}

Edit: I am also interested in a function to remove components from the entities field safely, although I have found this to be quite contradicting to the purpose of interior mutability.

You don't want &mut _ in your long-lived data structures, and a &mut Rc<T> isn't what you need to get a &mut T.

Here's an article about shared ownership and shared mutation. In short,

  • Rc<T> or Arc<T> give you shared ownership of T
    • You can borrow a &T from these, not a &mut T
  • If you need to get a &mut _ to something in the Rc or Arc, you need some shared (interior) mutability primitive inside the Rc or Arc:
    • Rc<RefCell<U>> or Arc<Mutex<U>> or Arc<RwLock<U>> for example

So in this case you probably want Rc<RefCell<dyn Component>>.


Here's a guess at what you're attempting. There may be better overall designs, I just went with your OP code as much as I could.

2 Likes

Thanks for the quick and good response @quinedot! It really helped me understand the differences between Rc and RefCell, but i still get a wierd error while integrating your answer to my code.
I want to run the on function in a method emit(&mut self, event: &UnrelatedEnum), but i get a wierd result from the borrow_mut() function.
I have tried to follow the type as explicitly as i can to demonstrate the issue:

pub fn emit(&mut self, event: &UnrelatedEnum) -> anyhow::Result<()> {
    if let Some(comps) = map {
        let comps: &mut Vec<Rc<RefCell<dyn Component>>> = comps;
        let component: RefMut<'static, (dyn Component + 'static> = comps.last().borrow_mut(); // This line does not compile with the mismatched types error below
        component.on();
    }
}

Error message:

expected RefMut<'_, (dyn Component + 'static)>, 
    found &mut &Rc<RefCell<dyn Component>>

This makes sense as Rc::borrow_mut() return &mut T, but i want to call the borrow_mut function of RefCell, as @quinedot does in the playground linked:

 let vec: &mut Vec<Rc<RefCell<dyn Component>>> = self.subscriptions.get_mut(&ue).unwrap();
        vec.push(Rc::new(RefCell::new(component)));
        let last: &Rc<RefCell<(dyn Component + 'static)>> = vec.last().unwrap();
        last.borrow_mut().on();

I have once again tried writing explicit types for everything to make it easier to debug, and this piece of code compiles. What is the problem with my code snippet?

You have hit an unfortunate name conflict with a different borrow_mut(). Your IDE has probably tried to help by importing the trait std::borrow::BorrowMut for you. You must remove that import (or add some explicit dereferencing to get the right borrow_mut() method selected, but that's trickier).

2 Likes

This is a footgun fired by rust-analyzer:

1 Like
Pedantic completionism for niche cases that don’t apply here

get_mut (if the Arc is actually unique) and make_mut (for copy-on-write behavior) will also give you &mut pointing inside the Arc without requiring extra interior mutability helpers.

2 Likes