Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot borrow as mutable more than once at a time

I'm trying to build a CHIP-8 emulator in Rust to learn the langauge. I'm currently stuck trying to solve this error the compiler gives me which I wrote in the title.

I will describe the current structure of the emulator and then I will indicate where it fails.

First of all I have VM struct defined as follows

pub struct VM {
    cpu: CPU,
    memory: Memory
}

and then I have the CPU struct which has a method defined as

pub fn execute(&mut self, vm: &mut VM) -> Result<(), &'static str> {
    // ....
}

Finally the method that fails is VM::cpu_execute defined as this

pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
   self.cpu.execute(&mut self)
}

This is where it fails.

I understand the error in and of itself, but in this context I really don't know how to fix it. The reason the code looks like this is so that the CPU and other VM modules can communicate: for example the CPU can access the memory by doing vm.memory() / vm.memory_mut().

I hope the question and the code is clear.

like image 844
Davide Carella Avatar asked Sep 16 '25 08:09

Davide Carella


1 Answers

In many other languages, you can pass a reference into a child object's method, but in Rust you can only do this without mut.

There are many ways to solve it, but I think the best way is to understand, that when you have VM = CPU + Mem, it's a design smell (in general, not only Rust) to call vm.cpu.execute(&mut vm), because what if:

impl CPU {
    fn execute(&mut self, vm: &mut VM) {
        vm.cpu = CPU::new(); // this is valid code
        // => if `self` is `vm.cpu` it now breaks `self` reference, so prevent this we are not allowed to borrow `mut` twice
    }
}

If you don't want to change your API, you can do this, but you leave your VM is a bad state for a short while, assuming that creating CPU is cheap.

use std::mem::take;

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect("should work");
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
        // take cpu and leave it initialized with default impl
        // => in other words self.cpu is useless for a small amount
        // of time now.
        let mut cpu = take(&mut self.cpu);
        let result = cpu.execute(self);
        self.cpu = cpu; // put it back
        
        result
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&mut self, vm: &mut VM) -> Result<(), &'static str> {
        // design smell: vm.cpu is useless and != self
        Ok(())
    }
}

#[derive(Default)]
struct Memory;

I think the best is to get rid of the design smell a go like this:

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect("should work");
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
        self.cpu.execute(&mut self.memory)
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&mut self, memory: &mut Memory) -> Result<(), &'static str> {
        Ok(())
    }
}

#[derive(Default)]
struct Memory;
like image 170
Aitch Avatar answered Sep 18 '25 10:09

Aitch