Reputation: 165
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.
Upvotes: 4
Views: 801
Reputation: 22402
This approach says pass the bits that are mutating separately (e.g. if your CPU is mutating the memory of the VM, just pass that to the execute):
#[derive(Default, Debug)]
struct CPU;
#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }
#[derive(Default, Debug)]
pub struct VM {
cpu: CPU,
memory: Memory
}
impl CPU {
pub fn execute(&mut self, mem: &mut Memory) -> Result<(), &'static str> {
println!("Executing");
mem.data = b"deadbeaf".to_vec();
Ok(())
}
}
impl VM {
pub fn run(&mut self) {
self.cpu.execute(&mut self.memory);
println!("CPU changed memory to {:#?}", self.memory);
}
}
pub fn main() {
let mut vm = VM::default();
vm.run();
}
Quite simple to understand and deal with
Somewhat limited, and you'd have to pass the stuff around.
This is also somewhat naïve in design:
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Default, Debug)]
struct CPU {
ip: u8, // instruction pointer
r1: u8, // register 1
r2: u8, // register 2
}
#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }
#[derive(Default, Debug)]
pub struct VM {
cpu: Rc<RefCell<CPU>>,
memory: Rc<RefCell<Memory>>
}
impl CPU {
pub fn execute(&mut self, vm: &VM) -> Result<(), &'static str> {
println!("Executing");
let mut mem = vm.memory.borrow_mut();
mem.data = b"deadbeaf".to_vec();
self.ip += 1; // increments instruction pointer.
Ok(())
}
}
impl VM {
pub fn run(&self) {
let mut cpu = self.cpu.borrow_mut();
cpu.execute(&self).unwrap();
println!("CPU changed memory to {:#?}", self.memory);
}
}
pub fn main() {
let vm = VM::default();
println!("VM: {:#?}", vm);
vm.run();
println!("VM: {:#?}", vm);
}
Here we are able to pass the VM around, and the resource attempts to borrow the appropriate resources mutably or immutably as needed.
Please take what I say as highly opinionated and with a grain of salt (in other words don't have to take what I say as the "right" way to do things).
I think that may be you should think about how to redesign the code so that there is only one "coordinator" (state machine) rather than saying each component (e.g. CPU) knowing about every other component.
RefCell
, here is Approach II Redux:use std::cell::RefCell;
#[derive(Default, Debug)]
struct CPU {
ip: u8, // instruction pointer
_r1: u8, // register 1
_r2: u8, // register 2
}
#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }
#[derive(Default, Debug)]
pub struct VM {
cpu: RefCell<CPU>,
memory: RefCell<Memory>
}
impl CPU {
pub fn execute(&mut self, vm: &VM) -> Result<(), &'static str> {
println!("Executing");
let mut mem = vm.memory.borrow_mut();
mem.data = b"deadbeaf".to_vec();
self.ip += 1; // increments instruction pointer.
Ok(())
}
}
impl VM {
pub fn run(&self) {
let mut cpu = self.cpu.borrow_mut();
cpu.execute(&self).unwrap();
println!("CPU changed memory to {:#?}", self.memory);
}
}
pub fn main() {
let vm = VM::default();
println!("VM: {:#?}", vm);
vm.run();
println!("VM: {:#?}", vm);
}
Upvotes: 0
Reputation: 1697
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;
Upvotes: 2
Reputation: 71380
You can pass only the VM
(make this an associated method) and access the CPU via the VM:
pub fn execute(vm: &mut VM) -> Result<(), &'static str> {
// ....
}
pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
CPU::execute(self)
}
But it may be better to take &mut self
and pass the memory separately.
Upvotes: 0