【问题标题】:Rust: Implement AVL Tree and error: thread 'main' panicked at 'already borrowed: BorrowMutError'Rust:实现 AVL 树和错误:线程“主”在“已经借用:BorrowMutError”时惊慌失措
【发布时间】:2020-03-25 11:24:40
【问题描述】:

我有以下树形结构:

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

type AVLTree<T> = Option<Rc<RefCell<TreeNode<T>>>>;

#[derive(Debug, PartialEq, Clone)]
struct TreeSet<T: Ord> {
    root: AVLTree<T>,
}

impl<T: Ord> TreeSet<T> {
    fn new() -> Self {
        Self { 
            root: None 
        }
    }

    fn insert(&mut self, value: T) -> bool {
        let current_tree = &mut self.root;

        while let Some(current_node) = current_tree {

            let node_key = &current_node.borrow().key;
          
            match node_key.cmp(&value) {
                Ordering::Less => { let current_tree = &mut current_node.borrow_mut().right; },
                Ordering::Equal => {
                    return false;
                }
                Ordering::Greater => { let current_tree = &mut current_node.borrow_mut().left; },
            }
        }

        *current_tree = Some(Rc::new(RefCell::new(TreeNode {
            key: value,
            left: None,
            right: None,
            parent: None
        })));

        true
    }

}


#[derive(Clone, Debug, PartialEq)]
struct TreeNode<T: Ord> {
    pub key: T,
    pub parent: AVLTree<T>,
    left: AVLTree<T>,
    right: AVLTree<T>,
}


fn main() {
    let mut new_avl_tree: TreeSet<u32> = TreeSet::new();
    new_avl_tree.insert(3);
    new_avl_tree.insert(5);
    println!("Tree: {:#?}", &new_avl_tree);
}

使用cargo build 构建很好,但是当我运行cargo run 时,出现以下错误:

线程“主”在“已经借用:BorrowMutError”时出现恐慌,src\libcore\result.rs:1165:5

注意:使用RUST_BACKTRACE=1 环境变量运行以显示回溯。错误:进程没有

成功退出:target\debug\avl-tree.exe(退出代码:101)

如果我只是打电话给insert(3),那就没问题了,我的树会被正确打印。但是,如果我在insert(3) 之后insert(5),我会得到那个错误。

我该如何解决这个问题?

【问题讨论】:

  • 您的代码存在逻辑问题:let current_tree = ... 创建了一个新的局部变量。然而,只有在循环中重新分配 current_tree 时,该算法才能工作。如果将current_tree 初始化为mut 并在重新分配时删除let,则会出现编译错误。然而,这是最容易的部分。

标签: rust avl-tree


【解决方案1】:

首先让我们更正您的算法。以下几行不正确:

let current_tree = &mut current_node.borrow_mut().right;
...
let current_tree = &mut current_node.borrow_mut().left;

两者都不会为current_tree 重新分配值,而是创建一个新的(未使用的)值(@Inline 将其称为名称阴影)。去掉let,改成current_treemut

现在我们得到一个编译器错误temporary value dropped while borrowed。可能编译器错误消息确实误导了您。它告诉您使用let 来增加生命周期,如果您在相同范围内使用结果,这将是正确的,但没有 let 可以将生命周期延长到超出范围。

问题是您不能传递对循环拥有的值的引用(如current_node.borrow_mut.right)。所以最好使用current_tree 作为自有变量。遗憾的是,这意味着您的代码中的许多巧妙技巧将不再起作用。

代码中的另一个问题是多次借用问题(您原来的运行时警告就是关于这个问题)。你不能在同一个RefCell 上调用borrow()borrow_mut() 而不惊慌(这就是RefCell 的目的)。

所以在发现代码中的问题后,我对如何编写代码产生了兴趣。现在它已经写好了,我认为分享它是公平的: fn insert(&mut self, value: T) -> bool { 如果让 None = self.root { self.root = TreeSet::root(value); 返回真; } 让 mut current_tree = self.root.clone();

  while let Some(current_node) = current_tree {
    let mut borrowed_node = current_node.borrow_mut();
    match borrowed_node.key.cmp(&value) {
      Ordering::Less => {
        if let Some(next_node) = &borrowed_node.right {
          current_tree = Some(next_node.clone());
        } else {
          borrowed_node.right = current_node.child(value);
          return true;
        }
      }
      Ordering::Equal => {
        return false;
      }
      Ordering::Greater => {
        if let Some(next_node) = &borrowed_node.left {
          current_tree = Some(next_node.clone());
        } else {
          borrowed_node.left = current_node.child(value);
          return true;
        }
      }
    };
  }
  true
}

//...

trait NewChild<T: Ord> {
  fn child(&self, value: T) -> AVLTree<T>;
}
impl<T: Ord> NewChild<T> for Rc<RefCell<TreeNode<T>>> {
  fn child(&self, value: T) -> AVLTree<T> {
    Some(Rc::new(RefCell::new(TreeNode {
      key: value,
      left: None,
      right: None,
      parent: Some(self.clone()),
    })))
  }
}

必须编写child(value:T)root(value:T) 这两个方法才能编译。

【讨论】:

  • 感谢您的详细回复。如果我的理解正确,Treeset::root 只会创建一个新的 Option&lt;Rc&lt;RefCell&lt;TreeNode&lt;T&gt;&gt;&gt;&gt; 吗? current_node.child(value) 是做什么的?它的类型是Rc&lt;RefCell&lt;TreeNode&lt;T&gt;&gt;&gt;Rc如何实现这个功能?
  • 你是对的。原始实现没有设置父级。这意味着,child(parent:Rc&lt;RefCell&lt;TreeNode&lt;T&gt;&gt;&gt;, value:T) 将是一个正确的签名。剩下的就是语法糖——我现在加了。
  • 我试过了,代码运行不停。某处可能存在循环引用。
  • 问题是你的println!("Tree: {:#?}", &amp;new_avl_tree);。您必须实现 DisplayDebug 并排除 the parent field from been displayed. Otherwise parent` 产生循环引用(如您所见)。
【解决方案2】:

由于语言中的内存安全规则,手动实现链表、树、图等数据结构不是新手的任务。我建议您阅读Too Many Linked Lists 教程,该教程讨论了如何以正确的方式在 Rust 中实现安全和不安全的链表。

还可以阅读name shadowing。 你的错误是在一个循环中你试图借用可变的东西,而这些东西已经被借用为不可变的。

let node_key = &current_node.borrow().key; // Borrow as immutable

match node_key.cmp(&value) {
    Ordering::Less => { let current_tree = &mut current_node.borrow_mut().right; }, // Create a binding which will be immediately deleted and borrow as mutable.

我建议你阅读 Rust book 来学习 rust。

【讨论】:

    猜你喜欢
    • 2020-12-18
    • 2020-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-20
    • 1970-01-01
    • 1970-01-01
    • 2021-11-03
    相关资源
    最近更新 更多