【问题标题】:Is it possible to mutate a struct's field inside a loop?是否可以在循环内改变结构的字段?
【发布时间】:2018-03-18 19:17:17
【问题描述】:

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

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

【问题讨论】:

标签: rust


【解决方案1】:

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

首先,您有许多方法在不需要时声明为采用&amp;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> { /* ... */ }

进行这些更改后,compiles for me

【讨论】:

    【解决方案2】:

    您可能不想从您的任何函数中返回Result&lt;_, &amp;str&gt;。如果你使用Result&lt;_, &amp;'static str&gt;Result&lt;_, String&gt;,你应该少用借用检查器。更好的是使用专门的错误类型,但这超出了这个答案的范围。

    返回Result&lt;_, &amp;str&gt; 存在问题的原因在于,它最终会将返回值的生命周期与self 的生命周期绑定在一起,这限制了您在结果生命周期内如何使用self

    【讨论】:

    • 最好使用专用的错误类型 — OP 已经在使用 Error 类型,所以大概他们已经了解这样做的机制。
    • 好点。当然,这只有助于返回该错误类型的函数......
    猜你喜欢
    • 1970-01-01
    • 2020-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多