迭代递归结构时无法获得可变引用:一次不能多次借用可变引用
Cannot obtain a mutable reference when iterating a recursive structure: cannot borrow as mutable more than once at a time
我正在尝试以迭代方式导航递归数据结构,以便在特定位置插入元素。以我有限的理解,这意味着对结构的根进行可变引用,然后用对其跟随者的引用依次替换它:
type Link = Option<Box<Node>>;
struct Node {
next: Link
}
struct Recursive {
root: Link
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(ref mut node) = *anchor {
anchor = &mut node.next;
}
anchor
}
}
然而,这失败了:
error[E0499]: cannot borrow `anchor.0` as mutable more than once at a time
--> src/main.rs:14:24
|
14 | while let Some(ref mut node) = *anchor {
| ^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first mutable borrow occurs here
...
18 | }
| - first borrow ends here
error[E0506]: cannot assign to `anchor` because it is borrowed
--> src/main.rs:15:13
|
14 | while let Some(ref mut node) = *anchor {
| ------------ borrow of `anchor` occurs here
15 | anchor = &mut node.next;
| ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `anchor` occurs here
error[E0499]: cannot borrow `*anchor` as mutable more than once at a time
--> src/main.rs:17:9
|
14 | while let Some(ref mut node) = *anchor {
| ------------ first mutable borrow occurs here
...
17 | anchor
| ^^^^^^ second mutable borrow occurs here
18 | }
| - first borrow ends here
这是有道理的,因为 anchor
和 node
指的是相同的结构,但实际上我在解构后不再关心 anchor
。
如何使用安全的 Rust 正确实现 back()
?
您可以使用递归来满足借用检查器的要求。这样做的缺点是会为列表中的每个项目创建堆栈框架。如果你的列表很长,这肯定会 运行 进入堆栈溢出。 LLVM会将Node::back
方法优化成一个循环(见playground上生成的LLVM IR)
impl Node {
fn back(&mut self) -> &mut Link {
match self.next {
Some(ref mut node) => node.back(),
None => &mut self.next,
}
}
}
impl Recursive {
fn back(&mut self) -> Option<&mut Link> {
self.root.as_mut().map(|node| node.back())
}
}
这是可能的...但我希望我有一个更优雅的解决方案。
诀窍不是从 anchor
借用,因此要在两个累加器之间兼顾:
- 一个持有对当前节点的引用
- 另一个被分配到下一个节点的引用
这让我想到:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
let tmp = anchor;
if let Some(ref mut node) = *tmp {
anchor = &mut node.next;
} else {
anchor = tmp;
break;
}
}
anchor
}
}
不是很漂亮,但这是借用检查器可以落后的东西所以¯\_(ツ)_/¯。
@ker 通过创建一个未命名的临时文件对此进行了改进:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
match {anchor} {
&mut Some(ref mut node) => anchor = &mut node.next,
other => return other,
}
}
}
}
这里的技巧是使用 {anchor}
将 anchor
的内容移动到匹配执行的未命名临时文件中。因此,在 match
块中,我们不是从 anchor
中借用,而是从临时块中借用,让我们可以自由修改 anchor
。请参阅相关博客 post Stuff the Identity Function Does (in Rust).
有效:
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while anchor.is_some(){
anchor = &mut {anchor}.as_mut().unwrap().next;
}
anchor
}
启用 non-lexical lifetimes 后,原始代码按原样工作:
type Link = Option<Box<Node>>;
struct Node {
next: Link,
}
struct Recursive {
root: Link,
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(node) = anchor {
anchor = &mut node.next;
}
anchor
}
}
非词法生命周期增加了编译器借用检查器的精度,允许它看到 anchor
的可变借用不再被使用。由于最近的语言更改,我们还可以简化 if let
中的关键字。
我正在尝试以迭代方式导航递归数据结构,以便在特定位置插入元素。以我有限的理解,这意味着对结构的根进行可变引用,然后用对其跟随者的引用依次替换它:
type Link = Option<Box<Node>>;
struct Node {
next: Link
}
struct Recursive {
root: Link
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(ref mut node) = *anchor {
anchor = &mut node.next;
}
anchor
}
}
然而,这失败了:
error[E0499]: cannot borrow `anchor.0` as mutable more than once at a time
--> src/main.rs:14:24
|
14 | while let Some(ref mut node) = *anchor {
| ^^^^^^^^^^^^
| |
| second mutable borrow occurs here
| first mutable borrow occurs here
...
18 | }
| - first borrow ends here
error[E0506]: cannot assign to `anchor` because it is borrowed
--> src/main.rs:15:13
|
14 | while let Some(ref mut node) = *anchor {
| ------------ borrow of `anchor` occurs here
15 | anchor = &mut node.next;
| ^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `anchor` occurs here
error[E0499]: cannot borrow `*anchor` as mutable more than once at a time
--> src/main.rs:17:9
|
14 | while let Some(ref mut node) = *anchor {
| ------------ first mutable borrow occurs here
...
17 | anchor
| ^^^^^^ second mutable borrow occurs here
18 | }
| - first borrow ends here
这是有道理的,因为 anchor
和 node
指的是相同的结构,但实际上我在解构后不再关心 anchor
。
如何使用安全的 Rust 正确实现 back()
?
您可以使用递归来满足借用检查器的要求。这样做的缺点是会为列表中的每个项目创建堆栈框架。如果你的列表很长,这肯定会 运行 进入堆栈溢出。 LLVM会将Node::back
方法优化成一个循环(见playground上生成的LLVM IR)
impl Node {
fn back(&mut self) -> &mut Link {
match self.next {
Some(ref mut node) => node.back(),
None => &mut self.next,
}
}
}
impl Recursive {
fn back(&mut self) -> Option<&mut Link> {
self.root.as_mut().map(|node| node.back())
}
}
这是可能的...但我希望我有一个更优雅的解决方案。
诀窍不是从 anchor
借用,因此要在两个累加器之间兼顾:
- 一个持有对当前节点的引用
- 另一个被分配到下一个节点的引用
这让我想到:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
let tmp = anchor;
if let Some(ref mut node) = *tmp {
anchor = &mut node.next;
} else {
anchor = tmp;
break;
}
}
anchor
}
}
不是很漂亮,但这是借用检查器可以落后的东西所以¯\_(ツ)_/¯。
@ker 通过创建一个未命名的临时文件对此进行了改进:
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
loop {
match {anchor} {
&mut Some(ref mut node) => anchor = &mut node.next,
other => return other,
}
}
}
}
这里的技巧是使用 {anchor}
将 anchor
的内容移动到匹配执行的未命名临时文件中。因此,在 match
块中,我们不是从 anchor
中借用,而是从临时块中借用,让我们可以自由修改 anchor
。请参阅相关博客 post Stuff the Identity Function Does (in Rust).
有效:
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while anchor.is_some(){
anchor = &mut {anchor}.as_mut().unwrap().next;
}
anchor
}
启用 non-lexical lifetimes 后,原始代码按原样工作:
type Link = Option<Box<Node>>;
struct Node {
next: Link,
}
struct Recursive {
root: Link,
}
impl Recursive {
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while let Some(node) = anchor {
anchor = &mut node.next;
}
anchor
}
}
非词法生命周期增加了编译器借用检查器的精度,允许它看到 anchor
的可变借用不再被使用。由于最近的语言更改,我们还可以简化 if let
中的关键字。