迭代递归结构时无法获取可变引用:一次不能多次借用可变引用

Cannot obtain a mutable reference when iterating a recursive structure: cannot borrow as mutable more than once at a time

提问人:Fabian Knorr 提问时间:6/23/2016 最后编辑:ShepmasterFabian Knorr 更新时间:11/20/2019 访问量:5199

问:

我正在尝试迭代导航递归数据结构,以便在特定位置插入元素。根据我有限的理解,这意味着对结构根的可变引用,并依次用对其追随者的引用来替换它:

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
    }
}

(Rust playground 链接)

但是,这会失败:

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

这两者都是有道理的,并且指的是相同的结构,但实际上在解构它之后我不再关心它了。anchornodeanchor

如何使用安全的 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}anchormatchanchoranchor

评论

0赞 Fabian Knorr 6/23/2016
棒!只是为了让我了解这里发生的事情:1) 具有初始引用 2) 从 中移动,这意味着它不再是引用 3) 可以安全地从中借用,因为它在循环迭代结束后立即被删除anchortmpanchoranchortmp
1赞 Matthieu M. 6/23/2016
最令人敬畏的是,我最初忘记了分支中的 rustc 为它提出了一个错误......无论如何,是的,这个想法是你不能在借用时重新赋值,所以我们将引用转移到然后借用来赋值。anchor = tmp;elseanchortmptmpanchor
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
    }
}

非词法生存期提高了编译器借用检查器的精度,使其能够看到不再使用可变借用。由于最近的语言变化,我们还可以简化其中的关键字。anchorif let