简体   繁体   中英

Trouble with bidirectional references using Option<Rc<RefCell<T>>>

I am building a NES emulator to learn Rust. I have difficulty organizing components.

My emulator uses the structure Bus to communicate with CPU and PPU. CPU and PPU also need to communicate with bus.

I figured that it is a good idea to create a shared pointer for Bus so that PPU and CPU have the same pointer reference to a bus. This is what I tried: playground . Unfortunately it didn't work.

error[E0308]: mismatched types
  --> src/main.rs:33:9
   |
32 |     fn bus(&mut self) -> &mut Bus {
   |                          -------- expected `&mut Bus` because of return type
33 |         self.bus_helper().borrow_mut()
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |         |
   |         expected `&mut Bus`, found struct `RefMut`
   |         help: consider mutably borrowing here: `&mut self.bus_helper().borrow_mut()`
   |
   = note: expected mutable reference `&mut Bus`
                         found struct `RefMut<'_, Bus>`

How to get my code to compile? Also, I wonder whether it is the best way to create bidirectional references? Are there any alternatives?

borrow_mut() returns a RefMut , which is a wrapper that maintains the borrow count of the cell so that the cell can panic if Rust's aliasing rules are violated at runtime (eg if a mutable reference and any other reference try to exist at the same time).

You can fix this by changing the return type to RefMut<'_, Bus> .

Alternatively, to make the RefCell implementation detail hidden from the caller, you can instead return impl DerefMut<Target=Bus> + '_ which says "this method returns something that can deref as a mutable Bus and which captures the lifetime of self ."

Also, I wonder whether it is the best way to create bidirectional references?

Generally for bidirectional references, you want all of the references away from the "primary value" (eg the root of a tree) to be Rc and references in the other direction to be Weak . This allows you to drop the primary value and have the whole network of structures be automatically destroyed. If you have two values with Rc s to each other then you have a reference cycle that must be explicitly broken to avoid a memory leak when you release your "top-level" reference to the network of structures.

However, if you find yourself wanting bidirectional references in this kind of case where you have a fixed set of values that want to refer to each other, you likely want a different pattern altogether. Based on your playground link, the CPU and PPU have strong shared ownership of the bus, and the bus has weak ownership back to the CPU and PPU. (In your model, you should express that with Weak and not raw pointers.)

What this hints at is that a single entity should own all of the components, and that entity should be responsible for their communication. There's a few ways you could model this.

  • A System value owns all of the components, and this value would provide a communication mechanism. This would replace Bus . The circular reference can be avoided by requiring a reference to System be given to the component values any time they need to do anything.
  • Channels could be used for communication between components, though this makes synchronous messaging between components trickier.
  • If the performance implications of channels are prohibitive and the desired API can't work by passing references to System around with every call, you could have references from the components back to System if you pin System so that it can't move, and therefore the references can't be invalidated. This is a bit of an advanced topic; you may want to read the relevant documentation .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM