error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/opcodes.rs:45:21
|
45 | 0x14 => self.increment_8(&mut self.registers.d, 1),
| ^^^^^-----------^---------------------^^^^
| | | |
| | | first mutable borrow occurs here
| | first borrow later used by call
| second mutable borrow occurs here
error[E0499]: cannot borrow `self.registers.d` as mutable more than once at a time
--> src/opcodes.rs:45:38
|
45 | 0x14 => self.increment_8(&mut self.registers.d, 1),
| -----------------^^^^^^^^^^^^^^^^^^^^^----
| | | |
| | | second mutable borrow occurs here
| | first borrow later used by call
| first mutable borrow occurs here
Why does increment_8 need to borrow self? What other state does it mutate besides the target register? (This is partly a rhetorical question. By borrowing self, the function almost certainly borrows more than it strictly needs, which leads to your double-borrow problem.)
/// Update the flags, just the zero flag as of now.
/// # Arguments
/// * `result` - The result of an operation, if it's zero the zero flag will be set.
fn update_flags(&mut self, result: u8) {
if result == 0 {
self.registers.f = 0x80;
} else {
self.registers.f = 0x00;
}
}
/// Add to a register and handle flags.
/// # Arguments
/// * `register` - Mutable reference to a register.
/// * `value` - The amount to add. It can also be negative.
fn increment_8(&mut self, register: &mut u8, amount: i16) -> (u8, u8) {
let test = 0;
if amount >= 0 {
*register = register.wrapping_add(amount as u8);
} else {
*register = register.wrapping_sub(amount as u8);
}
self.update_flags(*register);
(1, 1) // Every 8 bit INC/DEC instruction has 1 byte and 1 cycle
}
That would be the "free variables" or (with more work) "view struct" options explored in this article. (Side note, the closure RFC mentioned is part of stable Rust now.)
Another option is to pass something that tells it what register to use instead of passing a &mut to the register.
/// Add to a register and handle flags.
/// # Arguments
/// * `register` - Mutable reference to a register.
/// * `value` - The amount to add. It can also be negative.
fn increment_8(&mut self, register: RegistersName, amount: i16) -> (u8, u8) {
let register: &mut u8 = match register {
A => &mut self.registers.a,
B => &mut self.registers.b,
C => &mut self.registers.c,
D => &mut self.registers.d,
E => &mut self.registers.e,
F => &mut self.registers.f,
H => &mut self.registers.h,
L => &mut self.registers.l
};
if amount >= 0 {
*register = register.wrapping_add(amount as u8);
} else {
*register = register.wrapping_sub(amount as u8);
}
self.update_flags(*register);
(1, 1) // Every 8 bit INC/DEC instruction has 1 byte and 1 cycle
}
The borrow created by calling self.update_flags conflicts with the register borrow. You need to change update_flags so it is not a method (does not take &mut self)
but it might not always be feasible if you have complicated data structure. the real problem is how to design your type: don't create a type that contains all the states just for convenient, especially when you use mutable states a lot. shared mutability is very cumbersome in rust, and that's actually a good thing IMO.
in some OO languages like Java or C#, you are forced to create a "top level class" that represent "the whole program", while it's real purpose is just to provide a static method as the entry point. people tend to replicate what they are familiar with, and get frustrated with rust.
Every function with &mut self will have this problem, because &mut self means exclusive access to ALL fields of self, so it always forbids having any argument that borrows from the same object.
This is an unfortunate limitation of Rust, and you just can't use &mut self methods if you want to be passing fields (or any data they hold) by reference.
No, in the case where you get an error, register is still a borrow and it still exists when the subsequent function is being called. The compiler could in theory notice that it's only used for dereferencing and copying the pointed value, but in practice, that's a hard analysis (because it's still used and being read from).
In contrast, if you copy out of the borrow in a separate statement, then the compiler can see that the reference is not, in fact, being used in the subsequent function call, and so it can end the borrow early enough.