【问题标题】:Why does RefCell:borrow_mut result in a BorrowMutError when used on both sides of a short-circuiting boolean AND (&&)?为什么 RefCell:borrow_mut 在用于短路布尔 AND (&&) 的两侧时会导致 BorrowMutError?
【发布时间】:2020-11-17 16:16:28
【问题描述】:

我为 leetcode same tree problem 编写了这段代码:

use std::cell::RefCell;
use std::rc::Rc;

// Definition for a binary tree node.
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
    pub val: i32,
    pub left: Option<Rc<RefCell<TreeNode>>>,
    pub right: Option<Rc<RefCell<TreeNode>>>,
}

impl TreeNode {
    #[inline]
    pub fn new(val: i32) -> Self {
        TreeNode {
            val,
            left: None,
            right: None,
        }
    }
}

struct Solution;

impl Solution {
    pub fn is_same_tree(
        p: Option<Rc<RefCell<TreeNode>>>,
        q: Option<Rc<RefCell<TreeNode>>>,
    ) -> bool {
        match (p, q) {
            (None, None) => true,
            (Some(p), Some(q)) if p.borrow().val == q.borrow().val => {
                // this line won't work, it will cause BorrowMutError at runtime
                // but the `a & b` version works
                return (Self::is_same_tree(
                    p.borrow_mut().left.take(),
                    q.borrow_mut().left.take(),
                )) && (Self::is_same_tree(
                    p.borrow_mut().right.take(),
                    q.borrow_mut().right.take(),
                ));

                let a = Self::is_same_tree(p.borrow_mut().left.take(), q.borrow_mut().left.take());
                let b =
                    Self::is_same_tree(p.borrow_mut().right.take(), q.borrow_mut().right.take());
                a && b
            }
            _ => false,
        }
    }
}

fn main() {
    let p = Some(Rc::new(RefCell::new(TreeNode {
        val: 1,
        left: Some(Rc::new(RefCell::new(TreeNode::new(2)))),
        right: Some(Rc::new(RefCell::new(TreeNode::new(3)))),
    })));
    let q = Some(Rc::new(RefCell::new(TreeNode {
        val: 1,
        left: Some(Rc::new(RefCell::new(TreeNode::new(2)))),
        right: Some(Rc::new(RefCell::new(TreeNode::new(3)))),
    })));

    println!("{:?}", Solution::is_same_tree(p, q));
}

playground

thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:39:23

我认为&amp;&amp; 是一个短路运算符,这意味着两个表达式不会同时存在,所以两个可变引用不应该同时存在。

【问题讨论】:

  • 通过推断丢失的部分,我得到了this code,它实际上可以编译。该问题无法重现。您是否使用旧版本的编译器?
  • @E_net4ishere 我每晚使用 1.49.0。顺便说一句,我已经更新了我的问题,你可以在 Rust Playground 上试试。
  • 感谢您修复示例!我猜想 rust 编译器在为可变句柄执行析构函数时会立即考虑整个表达式。您可以通过将&amp;&amp; 的左侧拉到先前的声明中来解决此问题并仍然保持短路。

标签: rust refcell


【解决方案1】:

一个最小化的例子:

use std::cell::RefCell;

fn main() {
    let x = RefCell::new(true);
    *x.borrow_mut() && *x.borrow_mut();
}
thread 'main' panicked at 'already borrowed: BorrowMutError', src/main.rs:8:27

当您调用RefCell::borrow_mut 时,会返回一个RefMut 类型的临时值。来自the reference

临时的放置范围通常是封闭语句的结尾。

还有in expanded detail:

除了生命周期延长之外,表达式的临时范围是 包含表达式的最小范围,并且是其中之一 以下:

  • 整个函数体。
  • 声明。
  • ifwhileloop 表达式的主体。
  • if 表达式的 else 块。
  • ifwhile 表达式或 match 守卫的条件表达式。
  • 匹配臂的表达式。
  • lazy boolean expression 的第二个操作数。

如果扩展失败的代码,它将看起来像这样:

{
    let t1 = x.borrow_mut();

    *t1 && {
        let t2 = x.borrow_mut();
        *t2
    }
}

这显示了双借是如何发生的。

正如您已经注意到的,您可以通过提前声明一个变量来解决这个问题。这保留了短路性质:

let a = *x.borrow_mut();
a && *x.borrow_mut();

【讨论】:

  • 谢谢。执行“惰性布尔表达式的第二个操作数”。意味着在a &amp; b 中,a 将在 b 被评估之后被丢弃,而在a &amp; b &amp; c = (a &amp; b) &amp; c 中,a 和 be 在评估 c 之前将被丢弃。所以这解释了这个例子:play.rust-lang.org/…
  • 是的。在我的扩展代码中,您可以看到嵌套的{} 块包含t2in a &amp; b&amp; 不是懒惰/短路。只有&amp;&amp; 是。
  • 对不起。我刚起床,脑子里一片混乱。我的意思是&amp;&amp;,5 分钟后我无法编辑我的评论。它应该是:“惰性布尔表达式的第二个操作数”。表示在a &amp;&amp; b 中,a 将在评估b 之后被丢弃,而在a &amp;&amp; b &amp;&amp; c = (a &amp;&amp; b) &amp;&amp; c 中,ab 将在评估c 之前被丢弃。所以这解释了这个例子:play.rust-lang.org/…
猜你喜欢
  • 2016-07-03
  • 2020-10-25
  • 2015-02-15
  • 2021-12-14
  • 1970-01-01
  • 1970-01-01
  • 2011-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多