提问人:Fabian Knorr 提问时间:6/23/2016 最后编辑:ShepmasterFabian Knorr 更新时间:11/20/2019 访问量:5199
迭代递归结构时无法获取可变引用:一次不能多次借用可变引用
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()
答:
7赞
oli_obk
6/23/2016
#1
您可以使用递归来满足借用检查器的要求。这样做的缺点是为列表中的每个项目创建一个堆栈框架。如果你的列表很长,这肯定会遇到堆栈溢出。LLVM 会将方法优化成一个循环(参见 playground 上生成的 LLVM IR Node::back
)
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())
}
}
26赞
Matthieu M.
6/23/2016
#2
这是可能的......但我希望我有一个更优雅的解决方案。
诀窍是不要借用 ,因此在两个累加器之间切换: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,
}
}
}
}
这里的诀窍是,using 将 的内容移动到执行匹配的未命名临时内容中。因此,在块中,我们不是借用而是借用临时的,让我们可以自由修改。请参阅相关博客文章 Stuff the Identity Function Does (in Rust)。{anchor}
anchor
match
anchor
anchor
评论
0赞
Fabian Knorr
6/23/2016
棒!只是为了让我了解这里发生的事情:1) 具有初始引用 2) 从 中移动,这意味着它不再是引用 3) 可以安全地从中借用,因为它在循环迭代结束后立即被删除anchor
tmp
anchor
anchor
tmp
1赞
Matthieu M.
6/23/2016
最令人敬畏的是,我最初忘记了分支中的 rustc 为它提出了一个错误......无论如何,是的,这个想法是你不能在借用时重新赋值,所以我们将引用转移到然后借用来赋值。anchor = tmp;
else
anchor
tmp
tmp
anchor
0赞
Fabian Knorr
6/23/2016
这实际上可以写得非常简洁,因为我们可以在移动它之前调用它。我已经编辑了你的帖子。is_some()
anchor
0赞
oli_obk
6/23/2016
下面是没有临时或解包的解决方案版本:play.rust-lang.org/...
2赞
Matthieu M.
6/23/2016
@FabianKnorr:我不喜欢在可以避免的地方使用它,因为虽然它是安全的,但它也是(潜在)崩溃的根源。unwrap
2赞
aSpex
6/23/2016
#3
它的工作原理:
fn back(&mut self) -> &mut Link {
let mut anchor = &mut self.root;
while anchor.is_some(){
anchor = &mut {anchor}.as_mut().unwrap().next;
}
anchor
}
13赞
Shepmaster
5/19/2018
#4
启用非词法生存期后,原始代码将按原样工作:
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
下一个:可变对象与不可变对象
评论