为什么在短路布尔 AND (&&) 的两侧使用 RefCell:borrow_mut 会导致 BorrowMutError?

Shu*_*tsu 3 rust refcell

我为leetcode相同树问题编写了此代码:

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));
}
Run Code Online (Sandbox Code Playgroud)

操场

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));
}
Run Code Online (Sandbox Code Playgroud)

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

She*_*ter 5

一个最小化的例子:

use std::cell::RefCell;

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

当您调用 时RefCell::borrow_mut,将RefMut返回一个临时值类型。从参考

临时文件的删除范围通常是封闭语句的结尾。

详细说明

除了生命周期延长之外,表达式的临时作用域是包含该表达式的最小作用域,适用于以下情况之一:

  • 整个函数体。
  • 一份声明。
  • 一个的本体ifwhileloop表达。
  • 表达式的elseif
  • iforwhile表达式或match守卫的条件表达式。
  • 匹配臂的表达式。
  • 惰性布尔表达式的第二个操作数。

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

{
    let t1 = x.borrow_mut();

    *t1 && {
        let t2 = x.borrow_mut();
        *t2
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

let a = *x.borrow_mut();
a && *x.borrow_mut();
Run Code Online (Sandbox Code Playgroud)