首页 文章

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

提问于
浏览
15

我试图迭代地导航递归数据结构,以便在某个位置插入元素 . 根据我的有限理解,这意味着对结构的根进行可变引用,并通过对其跟随者的引用连续替换它:

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 link)

但是,这失败了:

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

这是有意义的,因为 anchornode 都指的是相同的结构,但我实际上在解构它之后不再关心 anchor .

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

4 回答

  • 8

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

    诀窍是不要从 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 . 请参阅相关博客文章Stuff the Identity Function Does (in Rust) .

  • 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())
        }
    }
    
  • 19

    原始代码按原样运行non-lexical lifetimes

    #![feature(nll)]
    
    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 main() {}
    

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

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

相关问题