在循环内改变结构体字段是否可行?

3
给出一个结构体,其中包含一些字节码和指令指针的结构体。它实现了获取(fetch)、解码(decode)和执行(execute)的模式。
use std::convert::TryFrom;

/// Trait for a virtual machine.
pub struct VirtualMachine {
    code: CodeMemory,
    instruction_pointer: usize,
}

impl VirtualMachine {
    pub fn new(byte_code: Vec<u8>) -> VirtualMachine {
        VirtualMachine {
            code: CodeMemory::new(byte_code),
            instruction_pointer: 0,
        }
    }

    /// Run a given program.
    pub fn run(&mut self) -> Result<(), &str> {
        loop {
            let opcode = self.fetch();

            if opcode.is_err() {
                return Err(opcode.unwrap_err());
            }

            let instruction = self.decode(opcode.unwrap());

            if instruction.is_err() {
                return Err("Bad opcode!");
            }

            let instruction = instruction.unwrap();

            if instruction == Instruction::Halt {
                return Ok(());
            }

            self.execute(instruction);
        }
    }

    fn fetch(&mut self) -> Result<u8, &str> {
        self.code.fetch(self.instruction_pointer)
    }

    fn decode(&mut self, opcode: u8) -> Result<Instruction, Error> {
        Instruction::try_from(opcode)
    }

    fn execute(&mut self, instruction: Instruction) {
        self.inc_instruction_pointer();

        match instruction {
            Instruction::Nop => (),
            Instruction::Halt => panic!("The opcode 'halt' should exit the loop before execute!"),
        }
    }

    fn inc_instruction_pointer(&mut self) {
        self.instruction_pointer += 1;
    }
}

struct CodeMemory {
    byte_code: Vec<u8>,
}

impl CodeMemory {
    fn new(byte_code: Vec<u8>) -> CodeMemory {
        CodeMemory { byte_code }
    }

    fn fetch(&self, index: usize) -> Result<u8, &str> {
        if index < self.byte_code.len() {
            Ok(self.byte_code[index])
        } else {
            Err("Index out of bounds!")
        }
    }

}

#[derive(Debug, PartialEq)]
pub enum Error {
    UnknownInstruction(u8),
    UnknownMnemonic(String),
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Instruction {
    Nop,
    // ...
    Halt,
}

impl TryFrom<u8> for Instruction {
    type Error = Error;

    fn try_from(original: u8) -> Result<Self, Self::Error> {
        match original {
            0x01 => Ok(Instruction::Nop),
            0x0c => Ok(Instruction::Halt),
            n => Err(Error::UnknownInstruction(n)),
        }
    }
}

编译器报错说:
error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:20:26
   |
18 |     pub fn run(&mut self) -> Result<(), &str> {
   |                - let's call the lifetime of this reference `'1`
19 |         loop {
20 |             let opcode = self.fetch();
   |                          ^^^^ mutable borrow starts here in previous iteration of loop
...
23 |                 return Err(opcode.unwrap_err());
   |                        ------------------------ returning this value requires that `*self` is borrowed for `'1`

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:26:31
   |
18 |     pub fn run(&mut self) -> Result<(), &str> {
   |                - let's call the lifetime of this reference `'1`
19 |         loop {
20 |             let opcode = self.fetch();
   |                          ---- first mutable borrow occurs here
...
23 |                 return Err(opcode.unwrap_err());
   |                        ------------------------ returning this value requires that `*self` is borrowed for `'1`
...
26 |             let instruction = self.decode(opcode.unwrap());
   |                               ^^^^ second mutable borrow occurs here

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:38:13
   |
18 |     pub fn run(&mut self) -> Result<(), &str> {
   |                - let's call the lifetime of this reference `'1`
19 |         loop {
20 |             let opcode = self.fetch();
   |                          ---- first mutable borrow occurs here
...
23 |                 return Err(opcode.unwrap_err());
   |                        ------------------------ returning this value requires that `*self` is borrowed for `'1`
...
38 |             self.execute(instruction);
   |             ^^^^ second mutable borrow occurs here

我认为我理解编译器描述的问题,但是我找不到一个安全的解决方案或模式来在Rust中实现它。是否可以在循环内部改变结构体字段?

我正在使用Rust 1.34来使用TryFrom特性。


抱歉,我从第一个示例中剥离了太多内容。整个项目有点大。 - Weltraumschaf
从示例中删除东西是好的;[MCVE]中的每个单词都很重要,包括“Minimal”。例如,您当前的代码有许多枚举变量没有发挥作用。将您的示例简化到最少仍然能够重现问题的程度是向我们展示您在在此处提问之前尝试解决自己的问题的一种方式。 - Shepmaster
去除了不必要的枚举变量。 - Weltraumschaf
2个回答

3

有两件事情阻止了你的代码示例编译。

首先,你声明了许多不需要使用&mut self的方法。

  • VirtualMachine::fetch只调用CodeMemory::fetch,后者不需要可变的self。

  • VirtualMachine::decode甚至没有访问VirtualMachine的任何字段。

其次,如@fintella's answer所指出的那样,CodeMemory::fetch将字符串切片作为错误返回。

你没有指定这个字符串切片的生命周期,因此它被推断为与CodeMemory实例的生命周期相同,而后者又与VirtualMachine实例的生命周期相联系。

这样做的效果是当你调用fetch时,不可变借用的生命周期持续了整个fetch的返回值范围,本例中几乎是整个循环的范围。

在这种情况下,你返回作为错误消息的字符串切片是一个字符串字面量,它具有静态作用域,所以你可以通过更改 CodeMemory::fetch 的定义来解决这个问题:
fn fetch(&self, index: usize) -> Result<u8, &'static str> { /* ... */ }

VirtualMachine::fetch到:

fn fetch(&self) -> Result<u8, &'static str> { /* ... */ }

在进行这些更改后,它对我编译


0

你可能不想从任何一个函数中返回Result<_, &str>。如果你使用Result<_, &'static str>或者Result<_, String>,你应该会遇到更少的借用检查问题。更好的方法是使用专门的错误类型,但这超出了本答案的范围。

返回Result<_, &str>的问题在于它将返回值的生命周期与self的生命周期绑定在一起,这限制了你在结果的生命周期内如何使用self


最好的方法是使用专门的错误类型——OP已经在使用Error类型,因此可以假定他们已经了解了如何做到这一点的机制。 - Shepmaster
好的观点。当然,这只有在返回该错误类型的函数上才有帮助... - fintelia

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接