I am a bit confused about what the problem is here, or rather why it happens in one scenario and does not in other.
I am writing a gameboy emulator and implementing a function that increments a register, this is the function, it works fine without any errors:
pub fn increment_reg8(
&mut self,
register_name: RegisterName,
register_byte: RegisterByteName,
) -> u64 {
let target_register = &mut self.registers[register_name][register_byte];
let original_value = *target_register;
*target_register += 1;
if *target_register == 0 {
self.set_flag(Flag::Zero);
} else {
self.clear_flag(Flag::Zero);
}
4
}
Now I have tried changing the if statement to
self.set_flag_to(Flag::Zero, *target_register == 0);
Which pretty much equals the same if statement from before
fn set_flag_to(&mut self, flag: Flag, value: bool) {
if value {
self.set_flag(flag);
} else {
self.clear_flag(flag);
}
}
Why does this change create double borrow error?
This is an interesting corner case of the borrow checker. self is being used by two things: set_flag (equivalently, clear_flag) and target_register, which is a mutable reference to data inside of self. Let's look at your first example.
*target_register += 1;
if *target_register == 0 {
self.set_flag(Flag::Zero);
} else {
self.clear_flag(Flag::Zero);
}
A naive borrow checker would reject this. target_register has a mutable reference to part of self, and set_flag / clear_flag need mutable access to all of self, so that's a double borrow.
But Rust's borrow checker isn't naive. It's quite clever. It sees that target_register is never used again, so it automatically drops the value in order to get the lease on self back. Rust just silently converted your code to this.
*target_register += 1;
if *target_register == 0 {
drop(target_register);
self.set_flag(Flag::Zero);
} else {
drop(target_register);
self.clear_flag(Flag::Zero);
}
Where drop is std::mem::drop.
Now your second example.
self.set_flag_to(Flag::Zero, *target_register == 0);
Again, set_flag_to wants a mutable reference to self, and target_register is already a mutable reference to self. Argument evaluation order in Rust happens left to right, and the self.method() syntax is syntax sugar for this.
YourType::set_flag_to(self, Flag::Zero, *target_register == 0);
So the self argument gets evaluated. This borrows all of self mutably. Flag::Zero isn't complicated, so it passes the borrow checker. But now we need to access target_register again. But we already borrowed self mutably, so we can't do that. And since we need target_register after the self borrow, we can't auto drop anything like we did before.
If you change the evaluation order so that target_register is evaluated before self, it'll pass again.
let is_zero = *target_register == 0;
self.set_flag_to(Flag::Zero, is_zero);
This will pass the borrow checker, since Rust silently converts it into
let is_zero = *target_register == 0;
drop(target_register);
self.set_flag_to(Flag::Zero, is_zero);
and we can borrow &mut self when we need to for that last line.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With