首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >有没有办法告诉锈迹,什么时候对某些借款过于保守?

有没有办法告诉锈迹,什么时候对某些借款过于保守?
EN

Stack Overflow用户
提问于 2021-11-22 17:37:46
回答 1查看 148关注 0票数 1

我正在玩在Rust中构建一个非常简单的基于堆栈的评估器,我遇到了一种奇怪的情况,我认为借贷检查器太保守了:

代码语言:javascript
复制
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);
}

我得到了:

代码语言:javascript
复制
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

我相信这是因为使用hashmap (f)的一部分永久地借用了所有的self,然后我将self不断地传递给f()。然而,这里并没有真正的冲突(我认为)。

我能够通过实际删除和重新插入以下值来解决这个问题:

代码语言:javascript
复制
    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(())
    }

但这感觉很烦人,而且更冗长,效率更低。我是不是遗漏了什么?有什么方法可以告诉编译器第一个版本没问题吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-11-22 17:59:01

编译器是完全正确的,您的解释也是完全正确的:对get()的调用需要借用self.ops来返回相同生命期的&Op。然后尝试用可变的FnMut调用self;这个可变的self别名借用是不可变的self.ops,理论上这个FnMut的实现可以通过self修改借用的Op,这是不允许的。编译器防止了通过别名指针发生变异的情况。

这种情况通常发生在传递&mut self时,因为不可变的借用到self的成员上,从而导致更多的借用(&self.ops.get()&self具有相同的生存期)“锁定”所有self

虽然第二个示例很麻烦,但它至少是正确的,编译器已经证明了这一点:通过从哈希表中删除OpFnMut不再能够通过self到达自己,并且在别名的同时防止了变异。

更好的方法通常是避免将&mut self作为参数(&mut self,在&mut Executor中)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70070092

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档