不能同时多次借用为可变的变量

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

Cannot borrow as mutable more than once at a time

问题

I'm trying to build a CHIP-8 emulator in Rust to learn the langauge. I'm currently stuck trying to solve this error the compiler gives me which I wrote in the title.

I will describe the current structure of the emulator and then I will indicate where it fails.

First of all I have VM struct defined as follows

pub struct VM {
    cpu: CPU,
    memory: Memory
}

and then I have the CPU struct which has a method defined as

pub fn execute(&mut self, vm: &mut VM) -> Result<(), &'static str> {
    // ....
}

Finally the method that fails is VM::cpu_execute defined as this

pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
   self.cpu.execute(&mut self)
}

This is where it fails.

I understand the error in and of itself, but in this context I really don't know how to fix it.
The reason the code looks like this is so that the CPU and other VM modules can communicate: for example the CPU can access the memory by doing vm.memory() / vm.memory_mut().

I hope the question and the code is clear.

英文:

I'm trying to build a CHIP-8 emulator in Rust to learn the langauge. I'm currently stuck trying to solve this error the compiler gives me which I wrote in the title.

I will describe the current structure of the emulator and then I will indicate where it fails.

First of all I have VM struct defined as follows

pub struct VM {
    cpu: CPU,
    memory: Memory
}

and then I have the CPU struct which has a method defined as

pub fn execute(&amp;mut self, vm: &amp;mut VM) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
    // ....
}

Finally the method that fails is VM::cpu_execute defined as this

pub fn cpu_execute(&amp;mut self) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
   self.cpu.execute(&amp;mut self)
}

This is where it fails.

I understand the error in and of itself, but in this context I really don't know how to fix it.
The reason the code looks like this is so that the CPU and other VM modules can communicate: for example the CPU can access the memory by doing vm.memory() / vm.memory_mut().

I hope the question and the code is clear.

答案1

得分: 1

在许多其他编程语言中,你可以将引用传递给子对象的方法,但在Rust中,只能在没有mut的情况下这样做。

有许多解决方法,但我认为最好的方法是理解,当你有VM = CPU + Mem时,调用vm.cpu.execute(&mut vm)是一个设计上的问题(通常情况下,不仅仅是Rust),因为如果:

impl CPU {
    fn execute(&mut self, vm: &mut VM) {
        vm.cpu = CPU::new(); // 这是有效的代码
        // => 如果`self`是`vm.cpu`,它现在会打破`self`引用,因此为了防止这种情况,我们不允许借用`mut`两次
    }
}

如果你不想改变你的API,你可以这样做,但你会在短时间内将VM留在一个糟糕的状态中,假设创建CPU是便宜的。

use std::mem::take;

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect("应该工作");
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
        // 获取CPU并将其初始化为默认实现
        // => 换句话说,现在self.cpu在短时间内无法使用
        let mut cpu = take(&mut self.cpu);
        let result = cpu.execute(self);
        self.cpu = cpu; // 把它放回去

        result
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&mut self, vm: &mut VM) -> Result<(), &'static str> {
        // 设计上的问题:vm.cpu是无用的,不等于self
        Ok(())
    }
}

#[derive(Default)]
struct Memory;

我认为最好的方法是消除设计上的问题,像这样做:

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect("应该工作");
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
        self.cpu.execute(&mut self.memory)
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&mut self, memory: &mut Memory) -> Result<(), &'static str> {
        Ok(())
    }
}

#[derive(Default)]
struct Memory;
英文:

In many other languages, you can pass a reference into a child object's method, but in Rust you can only do this without mut.

There are many ways to solve it, but I think the best way is to understand, that when you have VM = CPU + Mem, it's a design smell (in general, not only Rust) to call vm.cpu.execute(&amp;mut vm), because what if:

impl CPU {
    fn execute(&amp;mut self, vm: &amp;mut VM) {
        vm.cpu = CPU::new(); // this is valid code
        // =&gt; if `self` is `vm.cpu` it now breaks `self` reference, so prevent this we are not allowed to borrow `mut` twice
    }
}

If you don't want to change your API, you can do this, but you leave your VM is a bad state for a short while, assuming that creating CPU is cheap.

use std::mem::take;

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect(&quot;should work&quot;);
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&amp;mut self) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        // take cpu and leave it initialized with default impl
        // =&gt; in other words self.cpu is useless for a small amount
        // of time now.
        let mut cpu = take(&amp;mut self.cpu);
        let result = cpu.execute(self);
        self.cpu = cpu; // put it back
        
        result
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&amp;mut self, vm: &amp;mut VM) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        // design smell: vm.cpu is useless and != self
        Ok(())
    }
}

#[derive(Default)]
struct Memory;

I think the best is to get rid of the design smell a go like this:

fn main() {
    let mut vm = VM::default();
    vm.cpu_execute().expect(&quot;should work&quot;);
}

#[derive(Default)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl VM {
    pub fn cpu_execute(&amp;mut self) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        self.cpu.execute(&amp;mut self.memory)
    }
}

#[derive(Default)]
struct CPU;

impl CPU {
    pub fn execute(&amp;mut self, memory: &amp;mut Memory) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        Ok(())
    }
}

#[derive(Default)]
struct Memory;

答案2

得分: 0

方法 I: 简单方法

这种方法建议将变异的位分开处理(例如,如果您的 CPU 正在变异虚拟机的内存,只需将其传递给执行):

#[derive(Default, Debug)]
struct CPU;

#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }

#[derive(Default, Debug)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}

impl CPU {
    pub fn execute(&mut self, mem: &mut Memory) -> Result<(), &'static str> {
        println!("Executing");
        mem.data = b"deadbeaf".to_vec();
        Ok(())
    }    
}

impl VM {
    pub fn run(&mut self) {
        self.cpu.execute(&mut self.memory);
        println!("CPU changed memory to {:#?}", self.memory);
    }
}

pub fn main() {
    let mut vm = VM::default();
    vm.run();
}

优点:

相当简单易懂和处理

缺点:

有些有限,你需要传递相关的信息。

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=54836ccd4d8dc091846a2bb64df2d4f4

方法 II: 使用 Rc<RefCell>

这种方法在设计上也有点天真:

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Default, Debug)]
struct CPU {
    ip: u8, // 指令指针
    r1: u8, // 寄存器 1
    r2: u8, // 寄存器 2
}

#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }

#[derive(Default, Debug)]
pub struct VM {
    cpu: Rc<RefCell<CPU>>,
    memory: Rc<RefCell<Memory>>
}

impl CPU {
    pub fn execute(&mut self, vm: &VM) -> Result<(), &'static str> {
        println!("Executing");
        let mut mem = vm.memory.borrow_mut();
        mem.data = b"deadbeaf".to_vec();
        self.ip += 1; // 增加指令指针
        Ok(())
    }    
}

impl VM {
    pub fn run(&self) {
        let mut cpu = self.cpu.borrow_mut();
        cpu.execute(&self).unwrap();
        println!("CPU changed memory to {:#?}", self.memory);
    }
}

pub fn main() {
    let vm = VM::default();
    println!("VM: {:#?}", vm);
    vm.run();
    println!("VM: {:#?}", vm);
}

在这种方法中,我们可以传递 VM,并且资源尝试按需要以可变或不可变的方式借用适当的资源。

评论

请将我说的话视为高度主观和带有一定的保留(换句话说,不必认为我说的是“正确”的方法)。

我认为也许您应该考虑如何重新设计代码,以便只有一个“协调器”(状态机),而不是说每个组件(例如 CPU)都了解其他每个组件。

编辑 1: 正如在下面的评论中所指出的,我在“方法 II”中添加了多余的 RefCell,这是 Approach II Redux:

方法 II: 使用 RefCell

use std::cell::RefCell;

#[derive(Default, Debug)]
struct CPU {
    ip: u8, // 指令指针
    _r1: u8, // 寄存器 1
    _r2: u8, // 寄存器 2
}

#[derive(Default, Debug)]
struct Memory { data: Vec<u8> }

#[derive(Default, Debug)]
pub struct VM {
    cpu: RefCell<CPU>,
    memory: RefCell<Memory>
}

impl CPU {
    pub fn execute(&mut self, vm: &VM) -> Result<(), &'static str> {
        println!("Executing");
        let mut mem = vm.memory.borrow_mut();
        mem.data = b"deadbeaf".to_vec();
        self.ip += 1; // 增加指令指针
        Ok(())
    }    
}

impl VM {
    pub fn run(&self) {
        let mut cpu = self.cpu.borrow_mut();
        cpu.execute(&self).unwrap();
        println!("CPU changed memory to {:#?}", self.memory);
    }
}

pub fn main() {
    let vm = VM::default();
    println!("VM: {:#?}", vm);
    vm.run();
    println!("VM: {:#?}", vm);
}
英文:

Approach I: Simple approach

This approach says pass the bits that are mutating separately (e.g. if your CPU is mutating the memory of the VM, just pass that to the execute):


#[derive(Default, Debug)]
struct CPU;

#[derive(Default, Debug)]
struct Memory { data: Vec&lt;u8&gt; }

#[derive(Default, Debug)]
pub struct VM {
    cpu: CPU,
    memory: Memory
}


impl CPU {
    pub fn execute(&amp;mut self, mem: &amp;mut Memory) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        println!(&quot;Executing&quot;);
        mem.data = b&quot;deadbeaf&quot;.to_vec();
        Ok(())
    }    
}

impl VM {
    pub fn run(&amp;mut self) {
        self.cpu.execute(&amp;mut self.memory);
        println!(&quot;CPU changed memory to {:#?}&quot;, self.memory);
    }
}

pub fn main() {
    let mut vm = VM::default();
    vm.run();
}

Pros:

Quite simple to understand and deal with

Cons:

Somewhat limited, and you'd have to pass the stuff around.

https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=54836ccd4d8dc091846a2bb64df2d4f4

Approach II: Using Rc<RefCell<T>>

This is also somewhat naïve in design:

<strike>

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Default, Debug)]
struct CPU {
    ip: u8, // instruction pointer
    r1: u8, // register 1
    r2: u8, // register 2
}

#[derive(Default, Debug)]
struct Memory { data: Vec&lt;u8&gt; }

#[derive(Default, Debug)]
pub struct VM {
    cpu: Rc&lt;RefCell&lt;CPU&gt;&gt;,
    memory: Rc&lt;RefCell&lt;Memory&gt;&gt;
}


impl CPU {
    pub fn execute(&amp;mut self, vm: &amp;VM) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        println!(&quot;Executing&quot;);
        let mut mem = vm.memory.borrow_mut();
        mem.data = b&quot;deadbeaf&quot;.to_vec();
        self.ip += 1; // increments instruction pointer. 
        Ok(())
    }    
}

impl VM {
    pub fn run(&amp;self) {
        let mut cpu = self.cpu.borrow_mut();
        cpu.execute(&amp;self).unwrap();
        println!(&quot;CPU changed memory to {:#?}&quot;, self.memory);
    }
}

pub fn main() {
    let vm = VM::default();
    println!(&quot;VM: {:#?}&quot;, vm);
    vm.run();
    println!(&quot;VM: {:#?}&quot;, vm);
}

</strike>

https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=725587bae3d71159fe94530ac8542e68

Here we are able to pass the VM around, and the resource attempts to borrow the appropriate resources mutably or immutably as needed.

Commentary

Please take what I say as highly opinionated and with a grain of salt (in other words don't have to take what I say as the "right" way to do things).

I think that may be you should think about how to redesign the code so that there is only one "coordinator" (state machine) rather than saying each component (e.g. CPU) knowing about every other component.

Edit 1: As so correctly pointed out in the comment below the Approach II I had added superfluous RefCell, here is Approach II Redux:

Approach II: Using RefCell

use std::cell::RefCell;

#[derive(Default, Debug)]
struct CPU {
    ip: u8, // instruction pointer
    _r1: u8, // register 1
    _r2: u8, // register 2
}

#[derive(Default, Debug)]
struct Memory { data: Vec&lt;u8&gt; }

#[derive(Default, Debug)]
pub struct VM {
    cpu: RefCell&lt;CPU&gt;,
    memory: RefCell&lt;Memory&gt;
}


impl CPU {
    pub fn execute(&amp;mut self, vm: &amp;VM) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
        println!(&quot;Executing&quot;);
        let mut mem = vm.memory.borrow_mut();
        mem.data = b&quot;deadbeaf&quot;.to_vec();
        self.ip += 1; // increments instruction pointer. 
        Ok(())
    }    
}

impl VM {
    pub fn run(&amp;self) {
        let mut cpu = self.cpu.borrow_mut();
        cpu.execute(&amp;self).unwrap();
        println!(&quot;CPU changed memory to {:#?}&quot;, self.memory);
    }
}

pub fn main() {
    let vm = VM::default();
    println!(&quot;VM: {:#?}&quot;, vm);
    vm.run();
    println!(&quot;VM: {:#?}&quot;, vm);
}

答案3

得分: 0

你可以仅传递 VM(将其作为关联方法),然后通过 VM 访问 CPU:

pub fn execute(vm: &mut VM) -> Result<(), &'static str> {
    // ....
}

pub fn cpu_execute(&mut self) -> Result<(), &'static str> {
   CPU::execute(self)
}

但也许更好的做法是接受 &mut self,然后单独传递内存。

英文:

You can pass only the VM (make this an associated method) and access the CPU via the VM:

pub fn execute(vm: &amp;mut VM) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
    // ....
}

pub fn cpu_execute(&amp;mut self) -&gt; Result&lt;(), &amp;&#39;static str&gt; {
   CPU::execute(self)
}

But it may be better to take &amp;mut self and pass the memory separately.

huangapple
  • 本文由 发表于 2023年2月24日 00:23:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/75547567.html
匿名

发表评论

匿名网友

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

确定