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

Rust 迭代递归结构时无法获取可变引用:一次不能多次借用可变引用,rust,mutable,borrowing,Rust,Mutable,Borrowing,我试图迭代地导航递归数据结构,以便在特定位置插入元素。就我有限的理解而言,这意味着对结构的根进行可变引用,然后依次替换为对其跟随者的引用: type Link = Option<Box<Node>>; struct Node { next: Link } struct Recursive { root: Link } impl Recursive { fn back(&mut self) -> &mut Link {

我试图迭代地导航递归数据结构,以便在特定位置插入元素。就我有限的理解而言,这意味着对结构的根进行可变引用,然后依次替换为对其跟随者的引用:

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
    }
}
这是有意义的,因为
anchor
node
都引用了相同的结构,但我实际上不再关心
anchor
在对其进行解构之后


如何使用safe Rust正确实现
back()

您可以使用递归来满足借用检查器的要求。这样做的缺点是为列表中的每个项目创建堆栈框架。如果列表很长,这肯定会导致堆栈溢出。LLVM将把
节点::back
方法优化为一个循环(请参阅在上生成的LLVM IR)

impl节点{
fn返回(&mut self)->&mut链接{
匹配self.next{
Some(ref mut node)=>node.back(),
无=>&mut self.next,
}
}
}
简单递归{
fn返回(&mut self)->选项{
self.root.as_mut().map(| node | node.back())
}
}

有可能。。。但我希望我有一个更优雅的解决方案

诀窍是不要借用
锚点
,因此要在两个蓄能器之间切换:

  • 持有对当前节点的引用的节点
  • 另一个节点被分配到下一个节点的引用
这使我想到:

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}
锚定
的内容移动到一个未命名的临时文件中,匹配将在该临时文件上执行。因此,在
匹配
块中,我们不是从
锚定
中借用,而是从临时文件中借用,让我们可以自由修改
锚定
。请参阅相关的博客文章。

它起作用:

fn back(&mut self) -> &mut Link {
    let mut anchor = &mut self.root;
    while anchor.is_some(){
        anchor = &mut {anchor}.as_mut().unwrap().next;
    }
    anchor
}

一旦启用,原始代码将按原样工作:

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
    }
}
类型链接=选项;
结构节点{
下一篇:链接,
}
结构递归{
根:链接,
}
简单递归{
fn返回(&mut self)->&mut链接{
让mut锚定=&mut self.root;
而让一些(节点)=锚定{
锚点=&mut node.next;
}
锚
}
}

非词汇生存期提高了编译器借用检查器的精度,允许它看到不再使用
锚定
的可变借用。由于最近的语言更改,我们还可以简化
if let
中的关键字。

太棒了!让我了解这里发生了什么:1)
anchor
具有初始引用2)
tmp
anchor
移动,这意味着
anchor
不再是引用3)
tmp
可以安全地从中借用,因为它在循环迭代结束时就被丢弃了。这里最可怕的是,我最初忘记了
anchor=tmpelse
分支中的code>,rustc为此引发了一个错误。。。无论如何,是的,这个想法是在借用
anchor
时不能重新分配它,因此我们将引用转移到
tmp
,然后借用
tmp
分配
anchor
。这实际上可以写得非常简洁,因为我们可以在移动
anchor
之前调用
is_some()
。我已经编辑了你的帖子。这里有一个没有临时或展开的解决方案版本:@FabianKnorr:我不喜欢在我可以避免的地方使用
unwrap
,因为虽然它是安全的,但它也是(潜在)崩溃的根源。
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
    }
}