How to borrow mutable reference of owner to method of owned object


#1

I have run into a snag in my Z80 emulator, it’s most like a typical beginners error. I’m looking for advice on whether this is a thought error on my side, and how this could be solved the ‘Rust way’.

Here’s the situation:

I’m having a number of classes that implement different Z80 chips (CPU, PIO, CTC), and there’s a trait called Bus (basic idea taken from rustzx), which must be implemented to ‘tie the chips together’, a typical situation would be: the CPU executes an IN instruction to read the keyboard matrix, and from within the emulation core routine calls a method on Bus which implements what should happen. This Bus trait method then needs to call methods on other chips, which in turn may also need to call other Bus methods.

So the Bus is a generic callback interface which must be implemented differently for each emulated system, and its implementation describes how different chips in an emulated system are wired together.

All methods on those ‘chip classes’ that need to call out to the Bus take a mutable reference to an object that implements the Bus trait, mutable because I ultimately need to change the state of other chips, or the emulated system, for instance:

struct CPU {
  ...
}
impl CPU {
  pub fn step(&mut self, bus: &mut Bus) {
    // fetch and execute one instruction and 
    // call out into Bus if data must be externally provided
  }
}

A ‘System’ struct owns the different chip objects, and implements the Bus trait, for instance:

struct System {
  pub cpu: CPU,
  pub pio: PIO,
  pub ctc: CTC
}

impl Bus for System {

  pub fn cpu_in(&mut self, port: u16) -> u8 {
    // read some data from the PIO back into CPU, this
    // may in turn call Bus::pio_in()
    self.pio.read_data(self, port)
  }

  pub fn pio_in(&mut self, port: u16) -> u8 {
    ...read keyboard matrix and return value...
  }
}

Now the problem is that I need to pass a mutable reference to System (which implements the trait Bus) down into an object owned by System:

  let mut system = System::new();
  system.cpu.step(&mut system);

This produces the error “cannot borrow ‘system’ as mutable more than once at a time”, where the “first mutable borrow” is the system.cpu in the same line.

So my first question: is the borrow checker right, if yes why?

And the second question: any ideas how to fix this? The Bus trait implementation needs mutable access to all the chip objects (cpu, pio, ctc), may be I have a mental block, but no matter how many indirection layers I put inbetween it still is like a snake that bites its own tail :slight_smile:


#2

Perhaps System should hold each of its members wrapped in RefCell? Then you could pass it around immutably, and use borrow_mut for specific members as needed. Note that means you’ll now be delayed to a runtime panic if your snake bites its tail, through some callchain that recursively tries to mutate the same thing.

It’s of course nicer to have static checking, but that will require you to better separate the things being mutated, and I don’t have suggestions for that without knowing your program better.


#3

step seems to be a static method (no self parameter), so why do you call it like system.cpu.step(...) and not just CPU.step(...)?

OTOH I think a static method called on an object should not borrow that object mutably. That makes not sense to me.


#4

Typo, sorry, should be ‘pub fn step(&mut self, …)’, the step() method needs to access and modify the CPU object.


#5

Ok, in that case, the answer to your question

is: yes it is right.

You are trying to create two mutable references to (parts of) the system object, which violates the type system.
To make it a bit more obvious, you can rewrite it as:

let mut system = System::new();
CPU::step(&mut system.cpu, &mut system);

#6

Hmm ok, I guess that’s to prevent pointer aliasing on a very high level. I could come up with higher level concepts to communicate between objects like message passing or may be putting the objects into RefCells, but these all seem to add overhead at runtime. The idea to have one big owner object which also implements the trait to communicate between the sub-objects seemed like such a nice elegant solution (even better than what I currently have on the C++ side where I’m using callback functions). I’ll see if I can come up with a solution to untangle the pointer aliasing, but will probably do something else in the meantime to get my head clear :slight_smile:


#7

Ok, I worked around this for now using RefCell to wrap the ‘system chips’, and make all methods of the Bus trait take a &self instead of &mut self:

struct System {
  pub cpu: RefCell<CPU>,
  pub pio: RefCell<PIO>,
  pub ctc: RefCell<CTC>
}

This lets me access those chips independently as mutable, only problem would be if I want to manipulate e.g. the CPU object from inside a trait method that was called from a mutable-self CPU method itself, this would ‘bite the snake into the tail’ at runtime, but I think this situation is easily avoided.

I’ll continue to think about other solutions in the back of my head…


#8

So your solution is correct when you absolutely need a shared mutable state you have to resort to what in rust called “interior mutability”, and RefCell is one of the abstractions that help you implement that. You are right it comes with a runtime cost. Another abstraction that you can use is UnsafeCell that would require you to deal with raw pointers, which means you are in charge of prevention of data races, but runtime cost is completely up to you, it’s how you implement the access.