英文:
One function call creates double borrow error while other does not
问题
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:
我正在编写Gameboy模拟器,并实现一个增加寄存器值的函数,以下是该函数,它没有任何错误:
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
现在,我尝试将if语句更改为
self.set_flag_to(Flag::Zero, *target_register == 0);
Which pretty much equals the same if statement from before
这几乎等同于之前的if语句
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?
为什么这个改变会创建双重借用错误?
英文:
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?
答案1
得分: 8
这是借用检查器的一个有趣特例。self
被两个东西使用:set_flag
(等价于 clear_flag
)和 target_register
,它是对 self
内部数据的可变引用。让我们看看你的第一个例子。
*target_register += 1;
if *target_register == 0 {
self.set_flag(Flag::Zero);
} else {
self.clear_flag(Flag::Zero);
}
一个简单的借用检查器会拒绝这个。target_register
对 self
的一部分有可变引用,并且 set_flag
/ clear_flag
需要对 self
的所有部分进行可变访问,所以这是一个双重借用。
但是 Rust 的借用检查器并不简单。它非常聪明。它看到 target_register
不会再被使用,所以它自动放弃了值,以便重新获得对 self
的租借。Rust 只是悄悄地将你的代码转换成了这样。
*target_register += 1;
if *target_register == 0 {
drop(target_register);
self.set_flag(Flag::Zero);
} else {
drop(target_register);
self.clear_flag(Flag::Zero);
}
其中 drop
是 std::mem::drop
。
现在是你的第二个例子。
self.set_flag_to(Flag::Zero, *target_register == 0);
同样,set_flag_to
需要对 self
的可变引用,而 target_register
已经是对 self
的可变引用。在 Rust 中,参数求值顺序 是从左到右的,而 self.method()
语法是这个的语法糖。
YourType::set_flag_to(self, Flag::Zero, *target_register == 0);
所以 self
参数被求值。这会对 self
进行整体可变借用。Flag::Zero
不复杂,所以它通过了借用检查器。但是 现在 我们需要再次访问 target_register
。但是我们已经对 self
进行了可变借用,所以我们不能这样做。而且由于我们在 self
借用之后需要 target_register
,所以我们不能像之前那样自动放弃任何东西。
如果你改变求值顺序,使得 target_register
在 self
之前求值,它就会再次通过借用检查器。
let is_zero = *target_register == 0;
self.set_flag_to(Flag::Zero, is_zero);
这将通过借用检查器,因为 Rust 会悄悄地将它转换为
let is_zero = *target_register == 0;
drop(target_register);
self.set_flag_to(Flag::Zero, is_zero);
这样,当我们需要为最后一行借用 &mut self
时,它就可以正常通过。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论