book
book copied to clipboard
RefCell<Rc<?>> vs Rc<RefCell<?>> in Chapter 15.6
Chapter 15.6 of the Rust Book 2018 edition includes the following code:
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
This has a Rc inside a RefCell, but the pattern in other places and the previous section is put the RefCell inside the Rc. It would be nice if the book either used the more common idiom or explained why the rationale for not doing it.
I am actually not 100% sure why we chose this way. @carols10cents do you remember?
Any updates? That's an interesting point.
Any update @steveklabnik @carols10cents ?
I would say I feel better using Rc<RefCell<T>>. I'm building a small program which uses the Tree data structure and finally it looks like this:
pub type TreeNodeWeak = Option<Weak<RefCell<Node>>>;
pub type TreeNodeRc = Option<Rc<RefCell<Node>>>;
Most time, we need to do operations on the internal type T, the Rc, Weak and RefCell are just tools to allow us to do that. However, if we want to update a node in the tree, we will have to get a mutable reference, and Rc<RefCell<T>>
is so much easier, especially when you also need to handle Weak<RefCell<T>>
. So we can get an immutable reference from Rc
, and then get a mutable reference from RefCell
. In the other way (RefCell<Weak/Rc<T>>
), we first need a mutable reference to Weak/Rc
and get another mutable reference from Rc::get_mut()
. Getting one more mutable reference is so much more code to write...
Actually, I began the program with the example in the book, and I gave up that design very quickly and refactor the whole code to apply the change...
I think it's the difference between having multiple owners of the data (RefCell<Rc<T>>
) and having multiple owners of the read-write reference(Rc<RefCell<T>>
). I personally prefer the latter.
fwiw https://stackoverflow.com/questions/57367092/what-is-the-difference-between-rcrefcellt-and-refcellrct
Elaborating on the link by @anurbol:
The situations are quite different. In the book's example, we want to enable adding and removing children to a node in the tree. RefCell<T>
enables interior mutability for T, and it is the Vec<T>
we want to modify (by adding and removing elements). It is each of the Node
s that we want to have multiple potential owners, hence the Rc
around the node.
Putting RefCell
inside the Rc
presents two options.
Rc<Vec<RefCell<Node>>>
: This would mean that we have multiple references to a single vector of Node
s, which can all be modified independently. We would not be able to add or remove elements to such a vector because it is inside Rc
.
Rc<RefCell<Vec<Node>>>
: This would mean that we have multiple references to a single modifiable vector of Node
s. Both the vector and each of the Node
s would be modifiable, but we'd be sharing the same list of children
whenever we cloned (which isn't quite what we want either).
You're missing the option I'd suggest:
Vec<Rc<RefCell<Node>>
This option does allow you to add and remove children. The idea being that you always have a Rc<RefCell<Node>> and you can use .borrow_mut()
and friends to get mutable access to any of the fields on Node including the children.
That option only allows you to add and remove children if the Node isn't already wrapped in Rc<T>
, which it will be if it appears within the tree. How would you modify the children of a node that is already a child? You can't own that Node while it's already referenced by an Rc
in the tree, and thus you can't modify its children without RefCell::borrow_mut()
.
Good point though, I did miss that one.
Confused by your last comment.
Firstly, you say that you can't modify the Node if its been wrapped in an Rc
. But then you say you can't modify it without RefCell::borrow_mut()
, which means, yes, you can modify it.
Sorry for the confusion.
What I mean is that therefore you must use RefCell<Vec<...>>
. Oherwise you can't even call borrow_mut()
.
Hmm... no, if a node is in the tree it will be wrapped in Rc<RefCell<...>> which you can modify thanks to the RefCell.
Rust playground link to demonstrate what I'm talking about: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3095de5c78afa4daeda9a82bd3a28f75
Basically, putting the whole Node in a RefCell is more or less equivalent to putting the individual fields in a RefCell, but I think slightly cleaner.
Ah yeah. I see what you mean now. You're right, I didn't see how this approach could work before.
I should have led off with a code sample :)
Maybe, I think your explanation was good! I'm still very new to smart pointers.
Another thought, I actually think your approach lends itself better to a refactor to support concurrency. Multiple owners of a lock makes more sense than a separately owned locks of the same piece of data.
@WeYanish did you just copy and paste a stack overflow answer that was already linked to in this thread as if it was something you wrote?
Edit by @chriskrycho, 2024.04.02: Yeah, that's exactly what was going on; I deleted the comment referenced here.