在这个 leetcode 反转二叉树问题中,我试图可变地借用一个包含在 Rc 中的节点。这是代码。
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
pub fn invert_tree(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {
let mut stack: Vec<Option<Rc<RefCell<TreeNode>>>> = vec![root.clone()];
while stack.len() > 0 {
if let Some(node) = stack.pop().unwrap() {
let n: &mut TreeNode = &mut node.borrow_mut();
std::mem::swap(&mut n.left, &mut n.right);
stack.extend(vec![n.left.clone(), n.right.clone()]);
}
}
root
}
}
Run Code Online (Sandbox Code Playgroud)
如果我将这一行更改let n: &mut TreeNode为 just let n = &mut node.borrow_mut(),我会在下一行收到编译器错误,“*n一次不能多次借用可变的”,编译器似乎推断 n 为 类型&mut RefMut<TreeNode>,但是当我明确地显示时,一切都会正常说是吧&mut TreeNode。有什么理由吗?
借用拆分和解引用强制的组合会导致看似相同的代码表现不同。
编译器推断n其类型为RefMut<TreeNode>,因为这就是实际返回的内容borrow_mut:
Run Code Online (Sandbox Code Playgroud)pub fn borrow_mut(&self) -> RefMut<'_, T>
RefMut是一个有趣的小类型,其设计看起来像a &mut,但它实际上是一个单独的东西。它实现了Deref和,因此在需要时DerefMut它会很高兴地假装是 a 。&mut TreeNode但 Rust 仍在.deref()为您插入对其中的调用。
现在,为什么其中一个有效,而另一个却不起作用?如果没有类型注释,deref插入后,你会得到
let n = &mut node.borrow_mut();
std::mem::swap(&mut n.deref_mut().left, &mut n.deref_mut().right);
Run Code Online (Sandbox Code Playgroud)
因此,我们尝试在同一行中对同一变量调用deref_mut(需要 a )两次。&mut selfRust 的借用规则不允许这样做,所以它失败了。
(请注意,&mut第一行只是无缘无故地借用了一个拥有的值。临时生命周期延长让我们可以摆脱这个问题,即使&mut在这种情况下您根本不需要)
现在,另一方面,如果您确实放入了类型注释,那么 Rust 会看到borrow_mut返回 aRefMut<'_, TreeNode>但您要求 a &mut TreeNode,因此它会deref_mut在第一行插入 a 。你得到
let n: &mut TreeNode = &mut node.borrow_mut().deref_mut();
std::mem::swap(&mut n.left, &mut n.right);
Run Code Online (Sandbox Code Playgroud)
现在唯一的deref_mut调用是在第一行。然后,在第二行,我们同时访问n.left和n.right,两者都是可变的。看起来我们一次可变地访问两次,但 Rust 实际上足够聪明,可以看到我们同时访问两个不相交的部分,因此它允许这样做。这称为借用分割。Rust 会在不同的实例字段上拆分借用,但它不够智能,无法看到调用之间的拆分(原则上,函数调用可以执行任何操作,因此 Rust 的借用检查器拒绝尝试对其返回值进行高级推理)。nnderef_mut