Is Rc<RefCell<...>> a code smell?

  1. I’m currently writing some WebGL / GUI code, and I find myself using Rc<RefCell<...>> alot.

The basic story goes something like this:

  1. I have some object say Foo

  2. I need to refer to it from multiple places. Thus Rc<Foo>

  3. But I also need to make modifications to it sometimes. Thus I do

struct FooWrapper (Rc<RefCell<Foo>>)

and on FooWrapper, I define a number of “atomic” ops that are executed within a single self.0.borrow.mut() (the point being, the contents of these functions execute ‘atomically’)

  1. Is this a common design pattern? If so, is Rc<RefCell<...>> the right tool to use here? If not, what is the right way to do this?
1 Like

It is not a code smell to have shared, mutable ownership in your application. It is, however, a code smell if you have a lot of functions that accept Rc<RefCell<_>>. And it’s a code smell to have lots of have a lot of methods on them. Most of the time, interior mutability and shared ownership should be isolated to one small part of your application, while as much of it as possible should simply operate on &mut Foo or just &Foo.

For example, Servo’s layout system has a construct module and a traversal module that operate on the layout’s Arc<Flow> graph. Everything else operates on plain old &Flow, &mut Flow, &Fragment, and &mut Fragment.

6 Likes

&Foo and &mut Foo are fine if your function is just taking in a reference to a node and not doing anything persistent with it, but if you have a mutable graph and your function needs to change or store edges in the graph, you’ll need to take in Rc<RefCell<Foo>>s instead.

You’ll probably want to add a type alias to save on typing, since any code involving graph manipulation is going to need it.

Anyway, I don’t think there’s anything wrong with this. Keep in mind that you’re basically just doing the same thing that other OO languages do by default (be careful about cycles though, since there’s no GC). Not everything fits nicely into Rust’s ownership structure, and mutable graphs are one of the exceptions.

Edit: If your graph is acyclic, you may want to consider using copy on write. Rc makes this easy with the make_mut methods.

You can also consider having an Option in there, so that you can ‘free’ the structure when it’s time for it to die (by setting it to None), rather than wait for all the links to go away. That could also help break cycles.

Yeah, any sort of safe cycle collecting GC will require an option, since you have to break the cycles in order to actually free anything without using unsafe.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.