我正在尝试使用
Option
和 Box
而不使用 unsafe
模式来实现基本的链表实现,并且在尝试创建一种方法来删除具有特定值的所有节点时遇到了这个特别奇怪的错误。
我能够通过一个简化的示例重现错误(为了清晰起见,对某些类型进行了注释):
struct Node {
should_remove: bool,
next_node: Option<Box<Node>>,
}
struct SinglyLinkedList {
head: Option<Box<Node>>,
}
impl SinglyLinkedList {
fn remove_certain_nodes(&mut self) {
let mut current_node_link = &mut self.head;
while let Some(current_node /* &mut Box<Node> */) = current_node_link {
if current_node.should_remove {
// Remove the node by replacing the link to the current node with the link to the next node
let next_node_link = current_node.next_node.take();
std::mem::drop(current_node); // The error is the same with or without this
*current_node_link = next_node_link;
// current_node_link already points to the next node, so it doesn't need to be advanced
}
else {
current_node_link = &mut current_node.next_node;
}
}
}
}
编译出现错误:
error[E0506]: cannot assign to `*current_node_link` because it is borrowed
--> src/main.rs:18:17
|
13 | while let Some(current_node /* &mut Box<Node> */) = current_node_link {
| ------------ `*current_node_link` is borrowed here
...
18 | *current_node_link = next_node_link;
| ^^^^^^^^^^^^^^^^^^
| |
| `*current_node_link` is assigned to here but it was already borrowed
| borrow later used here
大多数时候,Rust 可以做一些违反借用的事情,只要以后不再使用借用即可。该错误似乎表明表达式
*current_node_link
以某种方式重用了与 current_node
相关的借用,我想不出对此的解释。
我尝试做一个
drop
的手册current_node
,如代码所示,但似乎没有帮助。
在表达式
*current_node_link
中“使用”借用的原因是什么?有没有办法可以重构我的逻辑来避免这个问题?
编辑: 这与借用检查器限制有关,该限制在每晚与 Polonius 一起解决。这个跟踪问题有更多细节。由于我要避免夜间功能,因此我当前的解决方法只是将
while let
条件替换为 while current_node_link.is_some()
,并将 current_node
的实例替换为 current_node_link.as_mut().unwrap()
,但我希望有更好的解决方案。
由于您的代码已被下一代借用检查器 Polonius 接受,因此它可能是有效的,除非存在 Polonius 错误。尽管如此,当前的借用检查器仍然错误地将其检测为有缺陷。解决借用检查器在理解循环方面的局限性的一种方法是切换到递归。例如,此代码已被接受:
fn remove_certain_nodes(&mut self) {
fn helper(current_node_link: &mut Option<Box<Node>>) {
let Some(current_node) = current_node_link else {
return;
};
if current_node.should_remove {
let next_node_link = current_node.next_node.take();
*current_node_link = next_node_link;
helper(current_node_link);
}
else {
helper(&mut current_node.next_node);
}
}
helper(&mut self.head);
}
与递归调用一样,您需要注意堆栈溢出,这不是双关语。此代码使用尾部调用(甚至考虑到丢弃),优化器可以将其转换为循环,并且似乎就是这种情况。但是 Rust 并不能保证消除尾部调用,并且在调试版本中还没有完成,所以你需要小心。