有没有办法告诉 rustc 什么时候对某些借用过于保守?
Is there a way to tell rustc when is is being too conservative about certain borrows?
我正在尝试用 Rust 构建一个非常简单的基于堆栈的求值器,我遇到了一个奇怪的情况,我 认为 借用检查器过于保守:
use std::collections::HashMap;
pub type Value = i32;
pub type Result = std::result::Result<(), Error>;
type Op = Box<dyn Fn(&mut Evaluator) -> Result>;
type OpTable = HashMap<String, Op>;
pub struct Evaluator {
stack: Vec<Value>,
ops: OpTable,
}
#[derive(Debug, PartialEq)]
pub enum Error {
DivisionByZero,
StackUnderflow,
UnknownWord,
InvalidWord,
}
impl Evaluator {
fn add(&mut self) -> Result {
if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
self.stack.push(y + x);
Ok(())
} else {
Err(Error::StackUnderflow)
}
}
fn sub(&mut self) -> Result {
if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
self.stack.push(y - x);
Ok(())
} else {
Err(Error::StackUnderflow)
}
}
pub fn new() -> Evaluator {
let stack: Vec<Value> = vec![];
let mut ops: OpTable = HashMap::new();
ops.insert("+".to_string(), Box::new(Evaluator::add));
ops.insert("-".to_string(), Box::new(Evaluator::sub));
Evaluator { stack, ops }
}
pub fn eval(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
// user definition
if let (Some(&":"), Some(&";")) = (symbols.first(), symbols.last()) {
if symbols.len() > 3 {
let statement = symbols[2..symbols.len() - 1].join(" ");
self.ops.insert(
symbols[1].to_string().to_ascii_lowercase(),
Box::new(move |caller: &mut Evaluator| caller.exec(&statement)),
);
return Ok(());
} else {
return Err(Error::InvalidWord);
}
}
self.exec(input)
}
fn exec(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
for sym in symbols {
if let Ok(n) = sym.parse::<i32>() {
self.stack.push(n);
} else {
let s = sym.to_ascii_lowercase();
if let Some(f) = self.ops.get(&s) { // <--------------errors here
f(self)?; // <----------------------------|
} else {
return Err(Error::InvalidWord);
}
}
}
Ok(())
}
}
fn main() {
let mut e = Evaluator::new();
e.eval("1 2 +");
println!("{:?}", e.stack);
e.eval(": plus-1 1 + ;");
e.eval("4 plus-1");
println!("{:?}", e.stack);
}
我得到:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:77:21
|
76 | if let Some(f) = self.ops.get(&s) {
| -------- immutable borrow occurs here
77 | f(self)?;
| -^^^^^^
| |
| mutable borrow occurs here
| immutable borrow later used by call
For more information about this error, try `rustc --explain E0502`.
error: could not compile `evaluator` due to previous error
我相信这是因为哈希图 (f
) 的一部分不可变地借用了所有 self
,然后我将 self
可变地传递给 f()
。然而,这里并没有真正的冲突(我认为)。
我可以通过实际删除并重新插入值来解决这个问题:
fn exec(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
for sym in symbols {
if let Ok(n) = sym.parse::<i32>() {
self.stack.push(n);
} else {
let s = sym.to_ascii_lowercase();
if self.ops.contains_key(&s) {
let f = self.ops.remove(&s).unwrap();
if let Err(e) = f(self) {
self.ops.insert(s, f);
return Err(e);
}
self.ops.insert(s, f);
} else {
return Err(Error::InvalidWord);
}
}
}
Ok(())
}
但这感觉很笨拙,而且更加冗长和低效。我错过了什么吗?有没有办法告诉编译器第一个版本没问题?
编译器是完全正确的,您的解释也是如此:对 get()
的调用需要借用 self.ops
到 return 和同一生命周期的 &Op
.然后,您尝试使用 self
的可变借用来调用 FnMut
;这个 self
的可变借用别名与 self.ops
上的不可变借用别名,理论上,这个 FnMut
的实现可以通过 [= 修改借用的 Op
14=],这是不允许的。编译器防止了通过别名指针发生突变的情况。
这种情况在传递 &mut self
时经常发生,因为 self
成员的不可变借用会导致更多借用(&self.ops.get()
与 &self
具有相同的生命周期) "锁定" 所有 of self
.
虽然你的第二个例子很麻烦,但它至少是正确的,正如编译器所证明的那样:通过从哈希表中删除 Op
,FnMut
无法通过 [=14= 到达自身],并且防止了别名时的突变。
更好的方法通常是避免将 &mut self
作为参数(&mut self
与 &mut Executor
中的一样)。
我正在尝试用 Rust 构建一个非常简单的基于堆栈的求值器,我遇到了一个奇怪的情况,我 认为 借用检查器过于保守:
use std::collections::HashMap;
pub type Value = i32;
pub type Result = std::result::Result<(), Error>;
type Op = Box<dyn Fn(&mut Evaluator) -> Result>;
type OpTable = HashMap<String, Op>;
pub struct Evaluator {
stack: Vec<Value>,
ops: OpTable,
}
#[derive(Debug, PartialEq)]
pub enum Error {
DivisionByZero,
StackUnderflow,
UnknownWord,
InvalidWord,
}
impl Evaluator {
fn add(&mut self) -> Result {
if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
self.stack.push(y + x);
Ok(())
} else {
Err(Error::StackUnderflow)
}
}
fn sub(&mut self) -> Result {
if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
self.stack.push(y - x);
Ok(())
} else {
Err(Error::StackUnderflow)
}
}
pub fn new() -> Evaluator {
let stack: Vec<Value> = vec![];
let mut ops: OpTable = HashMap::new();
ops.insert("+".to_string(), Box::new(Evaluator::add));
ops.insert("-".to_string(), Box::new(Evaluator::sub));
Evaluator { stack, ops }
}
pub fn eval(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
// user definition
if let (Some(&":"), Some(&";")) = (symbols.first(), symbols.last()) {
if symbols.len() > 3 {
let statement = symbols[2..symbols.len() - 1].join(" ");
self.ops.insert(
symbols[1].to_string().to_ascii_lowercase(),
Box::new(move |caller: &mut Evaluator| caller.exec(&statement)),
);
return Ok(());
} else {
return Err(Error::InvalidWord);
}
}
self.exec(input)
}
fn exec(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
for sym in symbols {
if let Ok(n) = sym.parse::<i32>() {
self.stack.push(n);
} else {
let s = sym.to_ascii_lowercase();
if let Some(f) = self.ops.get(&s) { // <--------------errors here
f(self)?; // <----------------------------|
} else {
return Err(Error::InvalidWord);
}
}
}
Ok(())
}
}
fn main() {
let mut e = Evaluator::new();
e.eval("1 2 +");
println!("{:?}", e.stack);
e.eval(": plus-1 1 + ;");
e.eval("4 plus-1");
println!("{:?}", e.stack);
}
我得到:
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:77:21
|
76 | if let Some(f) = self.ops.get(&s) {
| -------- immutable borrow occurs here
77 | f(self)?;
| -^^^^^^
| |
| mutable borrow occurs here
| immutable borrow later used by call
For more information about this error, try `rustc --explain E0502`.
error: could not compile `evaluator` due to previous error
我相信这是因为哈希图 (f
) 的一部分不可变地借用了所有 self
,然后我将 self
可变地传递给 f()
。然而,这里并没有真正的冲突(我认为)。
我可以通过实际删除并重新插入值来解决这个问题:
fn exec(&mut self, input: &str) -> Result {
let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
for sym in symbols {
if let Ok(n) = sym.parse::<i32>() {
self.stack.push(n);
} else {
let s = sym.to_ascii_lowercase();
if self.ops.contains_key(&s) {
let f = self.ops.remove(&s).unwrap();
if let Err(e) = f(self) {
self.ops.insert(s, f);
return Err(e);
}
self.ops.insert(s, f);
} else {
return Err(Error::InvalidWord);
}
}
}
Ok(())
}
但这感觉很笨拙,而且更加冗长和低效。我错过了什么吗?有没有办法告诉编译器第一个版本没问题?
编译器是完全正确的,您的解释也是如此:对 get()
的调用需要借用 self.ops
到 return 和同一生命周期的 &Op
.然后,您尝试使用 self
的可变借用来调用 FnMut
;这个 self
的可变借用别名与 self.ops
上的不可变借用别名,理论上,这个 FnMut
的实现可以通过 [= 修改借用的 Op
14=],这是不允许的。编译器防止了通过别名指针发生突变的情况。
这种情况在传递 &mut self
时经常发生,因为 self
成员的不可变借用会导致更多借用(&self.ops.get()
与 &self
具有相同的生命周期) "锁定" 所有 of self
.
虽然你的第二个例子很麻烦,但它至少是正确的,正如编译器所证明的那样:通过从哈希表中删除 Op
,FnMut
无法通过 [=14= 到达自身],并且防止了别名时的突变。
更好的方法通常是避免将 &mut self
作为参数(&mut self
与 &mut Executor
中的一样)。