在遍历列表时,借用的RefCell存活时间不够长

3

我正在尝试实现一个链接列表来理解Rust中的智能指针。我定义了一个Node

use std::{cell::RefCell, rc::Rc};

struct Node {
    val: i32,
    next: Option<Rc<RefCell<Node>>>,
}

并且像迭代一样循环

fn iterate(node: Option<&Rc<RefCell<Node>>>) -> Vec<i32> {
    let mut p = node;
    let mut result = vec![];

    loop {
        if p.is_none() {
            break;
        }

        result.push(p.as_ref().unwrap().borrow().val);

        p = p.as_ref().unwrap().borrow().next.as_ref();
    }

    result
}

编译器报错:

error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:27:13
   |
27 |         p = p.as_ref().unwrap().borrow().next.as_ref();
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^              -
   |             |                                         |
   |             |                                         temporary value is freed at the end of this statement
   |             |                                         ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `std::cell::Ref<'_, Node>`
   |             creates a temporary which is freed while still in use
   |             a temporary with access to the borrow is created here ...
   |
   = note: consider using a `let` binding to create a longer lived value

发生了什么?我们不能使用引用来迭代以这种方式定义的节点吗?

4
最近更新的《使用过多链表学习Rust》包含一节,试图解决这个确切的问题。我强烈建议您阅读它。章节介绍中包含免责声明:“[本]章节基本上是证明这是一个非常糟糕的想法。” - trent
2个回答

5

不要将借用引用分配给p,而是需要克隆Rc:

use std::cell::RefCell;
use std::rc::Rc;

struct Node {
    val: i32,
    next: Option<Rc<RefCell<Node>>>,
}

fn iterate(node: Option<Rc<RefCell<Node>>>) -> Vec<i32> {
    let mut p = node;
    let mut result = vec![];

    loop {
        let node = match p {
            None => break,
            Some(ref n) => Rc::clone(n), // Clone the Rc
        };

        result.push(node.as_ref().borrow().val); //works because val is Copy
        p = match node.borrow().next {
            None => None,
            Some(ref next) => Some(Rc::clone(next)), //clone the Rc
        };
    }

    result
}

fn main() {
    let node = Some(Rc::new(RefCell::new(Node {
        val: 0,
        next: Some(Rc::new(RefCell::new(Node { val: 1, next: None }))),
    })));

    let result = iterate(node);
    print!("{:?}", result)
}

这是必要的,因为您尝试在需要更长寿命的上下文中使用具有较短寿命的变量。 p.as_ref().unwrap().borrow() 的结果在循环迭代后被释放(即释放,解除分配),但是您正在尝试在下一个循环中使用其成员(这称为“使用后释放”,Rust 的设计目标之一是防止该问题)。
问题在于借用不拥有对象。 如果您想在下一个循环中将next作为p使用,则p将必须拥有该对象。这可以通过Rc(即“引用计数”)实现,并允许在单个线程中拥有多个所有者。
如果Node::next的定义为Option<Box<RefCell<Node>>>,如何遍历此列表?
是的,我也对RefCell感到非常困惑,没有RefCell,我们只能使用引用遍历列表,但使用RefCell会失败。 我甚至尝试添加Ref向量以保存引用,但仍无法成功。
如果您删除RefCell,则可以像这样遍历它:
struct Node {
    val: i32,
    next: Option<Box<Node>>,
}

fn iterate(node: Option<Box<Node>>) -> Vec<i32> {
    let mut result = vec![];
    let mut next = node.as_ref().map(|n| &**n);

    while let Some(n) = next.take() {
        result.push(n.val);

        let x = n.next.as_ref().map(|n| &**n);
        next = x;
    }

    result
}

fn main() {
    let node = Some(Box::new(Node {
        val: 0,
        next: Some(Box::new(Node { val: 1, next: None })),
    }));

    let result = iterate(node);
    print!("{:?}", result)
}

也许使用RefCell也是可能的,但我无法解决生命周期问题。

如果Node.next的定义是Option<Box<RefCell<Node>>>,如何遍历这个列表? - xudifsd
是的,我也对RefCell感到非常困惑。如果没有RefCell,我们只能使用引用来遍历列表,但是使用RefCell会失败。我甚至尝试添加一个Ref向量来保存引用,但仍然无法成功。 - xudifsd
如果您尝试保留所有单独的Ref,那么您实际上创建了一个自引用结构体,请参见此处以获取有关迭代嵌套的Rc<RefCell>的更多信息。 - kmdreko

0

我带来了与上面答案略有不同的代码,循环中只有一个匹配表达式。

fn iterate(node: Option<Rc<RefCell<ListNode>>>) -> Vec<i32>{
    let mut result = vec![];

    let mut p = match node{
        Some(x) => Rc::clone(&x),
        None => return result,
    };

    loop {
        result.push(p.as_ref().borrow().val); //works because val is Copy

        let node = match &p.borrow().next{
            Some(x) => Rc::clone(&x),
            None => break,
        };
        p = node;
    }

    result
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接