我正在尝试迭代语句向量并匹配(或双重匹配)评估它们的结果。我认为我的错误的根本原因是迭代中的不可变借用:
for stmt in &self.statements
如果我已经把自己逼到了墙角,我完全可以改变我的整个设计。
我认为在不可变借用期间可变借用是可以的,只要可变借用一次只有一个用户/消费者即可。
错误:
Checking rust-playground v0.1.0 (C:\Users\joraki\projects\rust-playground)
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src\main.rs:25:29
|
18 | for stmt in &self.statements {
| ----------------
| |
| immutable borrow occurs here
| immutable borrow later used here
...
25 | Err(err) => self.runtime_error(&err)
| ^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `rust-playground` due to previous error
这里指的是
for stmt in &self.statements
fn main() {
println!("Hello, world!");
}
#[derive(PartialEq, Debug)]
pub enum Stmt {
Expression(String),
Print(String)
}
pub struct Interpreter {
statements: Vec<Stmt>,
runtime_error: bool
}
impl Interpreter {
pub fn interpret(&mut self) {
for stmt in &self.statements {
let result = self.evaluate(stmt);
match result {
Ok(x) => match x {
Some(y) => println!("{}", y),
None => println!("No value returned. No errors.")
},
Err(err) => self.runtime_error(&err)
}
}
}
fn runtime_error(&mut self, err: &str) {
self.runtime_error = true;
println!("{}", err);
}
fn evaluate(&self, stmt: &Stmt) -> Result<Option<String>, String> {
match stmt {
Stmt::Expression(expr) => {
Ok(Some(expr.to_string()))
},
Stmt::Print(expr) => Err(format!("Something bad happened - {}", expr))
}
}
}
我认为在不可变借用期间可变借用是可以的,只要可变借用一次只有一个用户/消费者即可。
是和不是。借用不能重叠。如果你通过一个引用可变地借用,那么你必须改变这个引用,你不能有第二个。
例如在您的情况下:
for stmt in &self.statements
在整个循环期间借用 self
的一部分。现在,如果您在此期间被允许进行可变访问,则 runtime_error
可以修改 self.statements
。在大多数不是编译器错误的语言(如 C++)中,在迭代向量时修改向量会导致未定义的行为。由于 Rust 根据定义没有未定义的行为,这会导致编译器错误。
我认为有几种方法可以重构代码来解决这个问题。但这不是编译器的误报,这是一个实际问题。
您可以做的一件事是将
bool
替换为 AtomicBool
,它没有运行时开销,但可以通过内部可变性在不可变的情况下进行修改。这样,您的 runtime_error
函数就可以是不可变的:
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(PartialEq, Debug)]
pub enum Stmt {
Expression(String),
Print(String),
}
pub struct Interpreter {
statements: Vec<Stmt>,
runtime_error: AtomicBool,
}
impl Interpreter {
pub fn interpret(&mut self) {
for stmt in &self.statements {
let result = self.evaluate(stmt);
match result {
Ok(x) => match x {
Some(y) => println!("{}", y),
None => println!("No value returned. No errors."),
},
Err(err) => self.runtime_error(&err),
}
}
}
fn runtime_error(&self, err: &str) {
self.runtime_error.store(true, Ordering::Relaxed);
println!("{}", err);
}
fn evaluate(&self, stmt: &Stmt) -> Result<Option<String>, String> {
match stmt {
Stmt::Expression(expr) => Ok(Some(expr.to_string())),
Stmt::Print(expr) => Err(format!("Something bad happened - {}", expr)),
}
}
}
也就是说,如果我看看你的代码的意图(实现解释器),我认为将语句和解释器的状态分成两个不同的结构可能是一个更好的解决方案。
原始代码中真正的问题出现是因为 Rust 必须假设您可能会在
statements
方法中修改 runtime_error
。如果将它们分成两个结构,问题就会消失:
#[derive(PartialEq, Debug, Clone)]
pub enum Stmt {
Expression(String),
Print(String),
}
struct InterpreterState {
runtime_error: bool,
}
impl InterpreterState {
fn runtime_error(&mut self, err: &str) {
self.runtime_error = true;
println!("{}", err);
}
fn evaluate(&self, stmt: &Stmt) -> Result<Option<String>, String> {
match stmt {
Stmt::Expression(expr) => Ok(Some(expr.to_string())),
Stmt::Print(expr) => Err(format!("Something bad happened - {}", expr)),
}
}
}
pub struct Interpreter {
statements: Vec<Stmt>,
state: InterpreterState,
}
impl Interpreter {
pub fn interpret(&mut self) {
for stmt in &self.statements {
let result = self.state.evaluate(stmt);
match result {
Ok(x) => match x {
Some(y) => println!("{}", y),
None => println!("No value returned. No errors."),
},
Err(err) => self.state.runtime_error(&err),
}
}
}
}
这是因为编译器现在能够推断出您同时使用
&self.statements
和 &mut self.state
,它们不会重叠/冲突。
要解决该错误,您需要为
clone
结构派生 Interpreter
。在迭代 self.statements
之前,将 self
克隆到某个变量中并迭代该变量,然后您可以在 for 循环中使用 self
。
下面修改后的代码解决了问题中提到的错误。
#[derive(Clone)]
pub struct Interpreter {
statements: Vec<Stmt>,
runtime_error: bool
}
impl Interpreter {
pub fn interpret(&mut self) {
let mut interpretor = self.clone();
for stmt in &interpretor.statements {
let result = self.evaluate(stmt);
match result {
Ok(x) => match x {
Some(y) => println!("{}", y),
None => println!("No value returned. No errors.")
},
Err(err) => self.runtime_error(&err)
}
}
}
fn runtime_error(&mut self, err: &LoxError) {
self.runtime_error = true;
println!("{}", err);
}
}