rust 一个函数调用会产生双重借用错误,而另一个不会

4si2a6ki  于 2023-05-17  发布在  其他
关注(0)|答案(1)|浏览(136)

我有点困惑这里的问题是什么,或者更确切地说,为什么它发生在一个场景中,而不是在其他场景中。
我正在编写一个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
    }

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

self.set_flag_to(Flag::Zero, *target_register == 0);

它和前面的if语句差不多

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

为什么这种变化会产生双重借用错误?

rqqzpn5f

rqqzpn5f1#

这是一个有趣的角落情况下借用检查。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_registerself的一部分具有可变引用,而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);
}

其中dropstd::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参数被求值。这可变地借用了所有selfFlag::Zero并不复杂,所以它通过了借用检查器。但是 * 现在 * 我们需要再次访问target_register。但是我们已经可变地借用了self,所以我们不能这样做。由于我们需要target_register * 在 * self借用之后,我们不能像以前那样自动删除任何内容。
如果您更改求值顺序,使target_registerself之前求值,它将再次通过。

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

相关问题