26

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

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

如何back()使用安全的 Rust 正确实现?

4

4 回答 4

25

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

诀窍是不要借用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{anchor} 内容移动anchor到执行匹配的未命名临时文件中。因此,在match区块中我们不是从临时借来的,anchor而是从临时借来的,让我们可以自由地修改anchor. 请参阅相关博文Stuff the Identity Function Does (in Rust)

于 2016-06-23T09:12:48.810 回答
12

一旦启用非词法生命周期,原始代码将按原样工作:

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

非词法生命周期提高了编译器借用检查器的精度,允许它看到可变借用 ofanchor不再被使用。if let由于最近的语言变化,我们还可以简化关键字。

于 2018-05-18T16:28:33.800 回答
7

您可以使用递归来满足借用检查器。这具有为列表中的每个项目创建堆栈框架的缺点。如果您的列表很长,这肯定会遇到堆栈溢出。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())
    }
}
于 2016-06-23T09:10:59.333 回答
2

有用:

fn back(&mut self) -> &mut Link {
    let mut anchor = &mut self.root;
    while anchor.is_some(){
        anchor = &mut {anchor}.as_mut().unwrap().next;
    }
    anchor
}
于 2016-06-23T09:47:29.440 回答