迭代列表时,借用的 RefCell 持续时间不够长 [英] Borrowed RefCell does not last long enough when iterating over a list

查看:23
本文介绍了迭代列表时,借用的 RefCell 持续时间不够长的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

使用 std::{cell::RefCell, rc::Rc};结构节点{价值:i32,下一个:Option>,}

并像迭代一样

fn iterate(node: Option<&Rc<RefCell<Node>>>) ->Vec i32 ;{让 mut p = 节点;让 mut 结果 = vec![];环形 {如果 p.is_none() {休息;}result.push(p.as_ref().unwrap().borrow().val);p = p.as_ref().unwrap().borrow().next.as_ref();}结果}

编译器报错:

error[E0716]: 临时值在借用时丢失-->src/main.rs:27:13|27 |p = p.as_ref().unwrap().borrow().next.as_ref();|^^^^^^^^^^^^^^^^^^^^^^^^^^^-|||||在此语句结束时释放临时值||... 并且借用可能会在此处使用,当该临时文件被删除并运行类型为 `std::cell::Ref<'_, Node>` 的析构函数时|创建一个临时文件,该文件在仍在使用时被释放|这里创建了一个可以访问借用的临时文件......|= 注意:考虑使用 `let` 绑定来创建更长寿的值

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

解决方案

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

使用 std::cell::RefCell;使用 std::rc::Rc;结构节点{价值:i32,下一个:Option>,}fn iterate(node: Option>) ->Vec i32 ;{让 mut p = 节点;让 mut 结果 = vec![];环形 {让节点 = 匹配 p {无 =>休息,一些(参考 n)=>Rc::clone(n),//克隆 Rc};result.push(node.as_ref().borrow().val);//有效,因为 val 是 Copyp = 匹配 node.borrow().next {无 =>没有任何,一些(参考下一个)=>Some(Rc::clone(next)),//克隆 Rc};}结果}fn 主(){让节点 = Some(Rc::new(RefCell::new(Node {价值:0,下一个:一些(Rc::new(RefCell::new(Node { val: 1, next: None }))),})));让结果=迭代(节点);打印!({:?}",结果)}

这是必要的,因为您正试图在需要较长寿命的上下文中使用寿命较短的变量.p.as_ref().unwrap().borrow() 的结果在循环迭代后被丢弃(即释放、解除分配),但您正试图在下一个循环中使用其成员(这称为 use after free,Rust 的设计目标之一就是防止这种情况发生).

问题是借用对象不拥有该对象.如果您想在下一个循环中使用 next 作为 p,那么 p 将必须拥有该对象.这可以通过 Rc(即引用计数")来实现,并允许在单个线程中拥有多个所有者.

<块引用>

如果Node::next的定义是Option>>,如何遍历这个列表?

<块引用>

是的,我对 RefCell 也很困惑,如果没有 RefCell,我们可以仅使用引用遍历列表,但使用 RefCell 会失败>.我什至尝试添加一个 Ref 向量来保存引用,但仍然无法成功.

如果你删除 RefCell 你可以像这样迭代它:

struct 节点 {价值:i32,下一个: Option>,}fn iterate(node: Option>) ->Vec i32 ;{让 mut 结果 = vec![];让 mut next = node.as_ref().map(|n| &**n);而让 Some(n) = next.take() {结果.push(n.val);让 x = n.next.as_ref().map(|n| &**n);下一个 = x;}结果}fn 主(){让节点 = Some(Box::new(Node {价值:0,next: Some(Box::new(Node { val: 1, next: None })),}));让结果=迭代(节点);打印!({:?}",结果)}

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

I'm trying to implement a linked list to understand smart pointers in Rust. I defined a Node:

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

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

and iterate like

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
}

the compiler reports an error:

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

What happened? Can't we use a reference to iterate on a node defined this way?

解决方案

Instead of assigning p the borrowed reference, you need to clone the 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)
}

This is necessary because you are trying to use a variable with a shorter lifespan in a context that requires a longer lifespan. The result of p.as_ref().unwrap().borrow() is dropped (i.e. freed, de-allocated) after the loop iteration, but you are trying to use its members in the next loop (this is called use after free and one of the design goals of Rust is to prevent that).

The issue is that borrows do not own the object. If you want to use the next as p in the next loop, then p will have to own the object. This can be achieved with Rc (i.e. 'reference counted') and allows for multiple owners in a single thread.

What if the definition of Node::next is Option<Box<RefCell<Node>>>, how to iterate over this list?

Yes, I'm also very confused with RefCell, without RefCell we can iterate over list using reference only, but will fail with RefCell. I even tried to add a vector of Ref to save the reference, but still can not success.

If you drop the RefCell you can iterate it like this:

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

Maybe it's possible with a RefCell as well, but I was not able to work around the lifetime issues.

这篇关于迭代列表时,借用的 RefCell 持续时间不够长的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆