Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
winstonewert opened this issue Sep 24, 2018 · 16 comments
Open

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

winstonewert opened this issue Sep 24, 2018 · 16 comments
Milestone

Comments

@winstonewert
Copy link

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.

@steveklabnik
Copy link
Member

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

@davi2205
Copy link

Any updates? That's an interesting point.

@UnHumbleBen
Copy link
Contributor

Any update @steveklabnik @carols10cents ?

@lisanhu
Copy link

lisanhu commented Aug 25, 2019

I would say I feel better using Rc<RefCell>.
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.

@curlywurlycraig
Copy link

curlywurlycraig commented Sep 22, 2020

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

@winstonewert
Copy link
Author

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> and you can use .borrow_mut() and friends to get mutable access to any of the fields on Node including the children.

@curlywurlycraig
Copy link

curlywurlycraig commented Sep 23, 2020

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.

@winstonewert
Copy link
Author

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.

@curlywurlycraig
Copy link

Sorry for the confusion.

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

@winstonewert
Copy link
Author

winstonewert commented Sep 23, 2020

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.

@curlywurlycraig
Copy link

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

@winstonewert
Copy link
Author

I should have led off with a code sample :)

@curlywurlycraig
Copy link

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

@curlywurlycraig
Copy link

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.

@carols10cents carols10cents added this to the ch15 milestone Jul 16, 2021
@winstonewert

This comment was marked as resolved.

@rust-lang rust-lang deleted a comment from Jiashu-ht Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants