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

问题描述 投票:3回答:2

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

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特性。

rust
2个回答
3
投票

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

首先,你有许多方法被声明为在不需要时使用&mut self

  • VirtualMachine::fetch只调用CodeMemory::fetch,它不需要可变的自我。
  • 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> { /* ... */ }

compiles for me

在做出这些改变之后,它就是Result<_, &str>


0
投票

您可能不希望从任何函数返回Result<_, &'static str>。如果你使用Result<_, String>Result<_, &str>,你应该与借用检查器的斗争少得多。更好的方法是使用专用的错误类型,但这超出了本答案的范围。

返回self有问题的原因是它最终将返回值的生命周期与self的生命周期联系起来,这限制了在结果的生命周期中如何使用qazxswpoi。

© www.soinside.com 2019 - 2024. All rights reserved.