一个函数调用会产生双重借用错误,而其他函数则不会

Dan*_*iel 3 rust borrow-checker

我对这里的问题有点困惑,或者更确切地说,为什么它发生在一种情况下而不会发生在另一种情况下。

我正在编写一个 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
    }
Run Code Online (Sandbox Code Playgroud)

现在我尝试将 if 语句更改为

self.set_flag_to(Flag::Zero, *target_register == 0);
Run Code Online (Sandbox Code Playgroud)

这几乎等于之前的 if 语句

    fn set_flag_to(&mut self, flag: Flag, value: bool) {
        if value {
            self.set_flag(flag);
        } else {
            self.clear_flag(flag);
        }
    }
Run Code Online (Sandbox Code Playgroud)

为什么此更改会产生双重借用错误?

Sil*_*olo 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);
}
Run Code Online (Sandbox Code Playgroud)

天真的借用检查员会拒绝这一点。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);
}
Run Code Online (Sandbox Code Playgroud)

哪里。dropstd::mem::drop

现在是你的第二个例子。

self.set_flag_to(Flag::Zero, *target_register == 0);
Run Code Online (Sandbox Code Playgroud)

同样,set_flag_to想要一个对 的可变引用self,并且target_register已经是对 的可变引用selfRust 中的参数求值顺序是从左到右,self.method()语法是语法糖。

YourType::set_flag_to(self, Flag::Zero, *target_register == 0);
Run Code Online (Sandbox Code Playgroud)

所以这个self论点得到了评估。这一切都是self可变的。Flag::Zero并不复杂,因此它通过了借用检查器。但现在我们需要target_register再次访问。但我们已经self可变借贷了,所以我们不能这样做。而且由于我们需要在借用target_register 之后self,我们不能像以前那样自动删除任何东西。

如果您更改评估顺序以便target_register在之前进行评估self,它将再次通过。

let is_zero = *target_register == 0;
self.set_flag_to(Flag::Zero, is_zero);
Run Code Online (Sandbox Code Playgroud)

这将通过借用检查器,因为 Rust 会默默地将其转换为

let is_zero = *target_register == 0;
drop(target_register);
self.set_flag_to(Flag::Zero, is_zero);
Run Code Online (Sandbox Code Playgroud)

&mut self当我们需要时我们可以借用最后一行。