book icon indicating copy to clipboard operation
book copied to clipboard

RefCell<Rc<?>> vs Rc<RefCell<?>> in Chapter 15.6

Open winstonewert opened this issue 6 years ago • 16 comments

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.

winstonewert avatar Sep 24 '18 15:09 winstonewert

I am actually not 100% sure why we chose this way. @carols10cents do you remember?

steveklabnik avatar Jan 07 '19 19:01 steveklabnik

Any updates? That's an interesting point.

davi2205 avatar Jun 20 '19 14:06 davi2205

Any update @steveklabnik @carols10cents ?

UnHumbleBen avatar Aug 04 '19 05:08 UnHumbleBen

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.

lisanhu avatar Aug 25 '19 04:08 lisanhu

fwiw https://stackoverflow.com/questions/57367092/what-is-the-difference-between-rcrefcellt-and-refcellrct

anurbol avatar Mar 06 '20 13:03 anurbol

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 Nodes 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 Nodes, 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 Nodes. Both the vector and each of the Nodes would be modifiable, but we'd be sharing the same list of children whenever we cloned (which isn't quite what we want either).

curlywurlycraig avatar Sep 22 '20 21:09 curlywurlycraig

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.

winstonewert avatar Sep 22 '20 22:09 winstonewert

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.

curlywurlycraig avatar Sep 23 '20 14:09 curlywurlycraig

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.

winstonewert avatar Sep 23 '20 14:09 winstonewert

Sorry for the confusion.

What I mean is that therefore you must use RefCell<Vec<...>>. Oherwise you can't even call borrow_mut().

curlywurlycraig avatar Sep 23 '20 14:09 curlywurlycraig

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.

winstonewert avatar Sep 23 '20 14:09 winstonewert

Ah yeah. I see what you mean now. You're right, I didn't see how this approach could work before.

curlywurlycraig avatar Sep 23 '20 14:09 curlywurlycraig

I should have led off with a code sample :)

winstonewert avatar Sep 23 '20 14:09 winstonewert

Maybe, I think your explanation was good! I'm still very new to smart pointers.

curlywurlycraig avatar Sep 23 '20 14:09 curlywurlycraig

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.

curlywurlycraig avatar Sep 23 '20 14:09 curlywurlycraig

@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.

winstonewert avatar Jul 16 '23 00:07 winstonewert