One function call creates double borrow error while other does not.

huangapple go评论54阅读模式
英文:

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_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 参数被求值。这会对 self 进行整体可变借用。Flag::Zero 不复杂,所以它通过了借用检查器。但是 现在 我们需要再次访问 target_register。但是我们已经对 self 进行了可变借用,所以我们不能这样做。而且由于我们在 self 借用之后需要 target_register,所以我们不能像之前那样自动放弃任何东西。

如果你改变求值顺序,使得 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 时,它就可以正常通过。

英文:

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.

huangapple
  • 本文由 发表于 2023年5月14日 06:20:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/76245099.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定