是否有可能在循环中改变struct的字段?

时间:2018-03-18 19:17:18

标签: rust

给定是一个结构,它包含一个包含一些字节代码和指令指针的结构。它实现了获取,解码和执行的模式:

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中实现它。是否有可能在循环中改变struct字段?

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

2 个答案:

答案 0 :(得分:3)

阻止代码示例编译有两件事。

首先,当他们不需要时,你有许多方法被宣布为&mut self

  • VirtualMachine::fetch只调用CodeMemory::fetch,不需要自我变异。

  • VirtualMachine::decode甚至无法访问VirtualMachine

  • 的任何字段

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

您没有指定此字符串切片的生命周期,因此推断它与CodeMemory实例的生命周期相同,而VirtualMachine实例的生命周期又与{{1}的生命周期相关联。实例。

这样做的结果是,当你调用fetch时,不可变借用的生命周期持续到fetch的返回值的整个范围 - 在这种情况下,几乎整个循环

在这种情况下,您作为错误消息返回的字符串切片是一个字符串文字,它具有静态范围,因此您可以通过将CodeMemory::fetch的定义更改为:

来解决此问题。
fn fetch(&self, index: usize) -> Result<u8, &'static str> { /* ... */ }

VirtualMachine::fetch

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

进行这些更改后,compiles for me

答案 1 :(得分:0)

您可能不希望从任何功能返回Result<_, &str>。如果您使用Result<_, &'static str>Result<_, String>,那么借用核查人员的斗争就会少得多。更好的方法是使用专用的错误类型,但这超出了这个答案的范围。

返回Result<_, &str>有问题的原因是它最终将返回值的生命周期与self的生命周期联系起来,这限制了在self期间使用git check-ignore -v .idea/bbb.txt .idea/workspace.xml .idea/runConfigurations/Android_Debug___ReactNative.xml android/.idea/aaaa.txt android/.idea/runConfigurations/app.xml 的方式结果的一生。