Rust中,如何从Rc<RefCell<T>>中复制内部值并返回它?

7

简介:我对Rust还很陌生,所以决定通过实现一个双向链表来练习。为了调试方便,我实现了get()方法,但是我无法从Rc<RefCell<_>>中复制值出来。(抱歉问了个愚蠢的问题)

问题:我试图在.get()中返回一个Result<T, &'static str>,其中T是节点中存储的数据类型,&str是错误消息字符串。借用检查器告诉我不能返回一个方法内部变量的引用,所以我尝试将内部值复制出来并返回,但是失败了。

源代码

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

struct Node<T> {
    data: Option<T>,
    prev: Option<Rc<RefCell<Node<T>>>>,
    next: Option<Rc<RefCell<Node<T>>>>,
}

impl<T> Node<T> {
    /// Instantiate a new dummy node.
    /// This node is used to mark the start and end of the list.
    /// It is not counted in the size of the list.
    fn new() -> Self {
        Node {
            data: None,
            prev: None,
            next: None,
        }
    }
    /// Instantiate a new content node.
    /// This node is used to store data.
    /// It is counted in the size of the list.
    fn from(data: T) -> Self {
        Node {
            data: Some(data),
            prev: None,
            next: None,
        }
    }
}

struct List<T> {
    head: Rc<RefCell<Node<T>>>,
    tail: Rc<RefCell<Node<T>>>,
    size: usize,
}

impl<T> List<T> {
    pub fn new() -> Self {
        let head = Rc::new(RefCell::new(Node::new()));
        let tail = Rc::new(RefCell::new(Node::new()));
        head.borrow_mut().next = Some(Rc::clone(&tail));
        tail.borrow_mut().prev = Some(Rc::clone(&head));
        List { head, tail, size: 0 }
    }
    pub fn prepend(&self, data: T) {
        let node = Rc::new(RefCell::new(Node::from(data)));
        let mut head = self.head.borrow_mut();
        
        node.borrow_mut().next = Some(head.next.take().unwrap());
        node.borrow_mut().prev = Some(Rc::clone(&self.head));
        head.next = Some(Rc::clone(&node));
        if let Some(next) = node.borrow().next.as_ref() {
            next.borrow_mut().prev = Some(Rc::clone(&node));
        };
    }
    pub fn append(&self, data: T) {
        let node = Rc::new(RefCell::new(Node::from(data)));
        let mut tail = self.tail.borrow_mut();
        
        node.borrow_mut().prev = Some(Rc::clone(&tail.prev.take().unwrap()));
        node.borrow_mut().next = Some(Rc::clone(&self.tail));
        tail.prev = Some(Rc::clone(&node));
        if let Some(prev) = node.borrow().prev.as_ref() {
            prev.borrow_mut().next = Some(Rc::clone(&node));
        };
    }
    pub fn get(&self, index: isize) -> Result<T, &'static str> {
        let mut current: Rc<RefCell<Node<T>>> = Rc::clone(self.head.borrow().next.as_ref().unwrap());
        for _ in 0..index {
            let tmp = Rc::clone(current.borrow().next.as_ref().ok_or("Index out of range")?);
            current = tmp;
        }
        let result = current.borrow().data.as_ref().ok_or("Index out of range")?;  // error[E0716]
        Ok(*result)  // error[E0507]
    }
}
/*
error[E0716]: temporary value dropped while borrowed
  --> src\linked.rs:74:22
   |
74 |         let result = current.borrow().data.as_ref().ok_or("Index out of range")?;
   |                      ^^^^^^^^^^^^^^^^                                           - temporary value is freed at the end of this statement
   |                      |
   |                      creates a temporary value which is freed while still in use
75 |         Ok(*result)
   |            ------- borrow later used here
   |
help: consider using a `let` binding to create a longer lived value
   |
74 ~         let binding = current.borrow();
75 ~         let result = binding.data.as_ref().ok_or("Index out of range")?;
   |

error[E0507]: cannot move out of `*result` which is behind a shared reference
  --> src\linked.rs:75:12
   |
75 |         Ok(*result)
   |            ^^^^^^^ move occurs because `*result` has type `T`, which does not implement the `Copy` trait
*/

我已经尝试过

还有:如果其中一篇帖子解决了我的问题,但我没有正确实施,请原谅我,并请教我如何正确操作。非常感谢。


8
我对Rust还很陌生,所以我决定通过实现一个双向链表来练习。但是,这个想法并不好。不要通过实现链表来练习Rust,尤其是双向链表。这样做不会教你Rust的惯用方式或良好的编程风格,只会让你更加困惑。 - Chayim Friedman
2
请阅读此链接:https://rust-unofficial.github.io/too-many-lists/ - Toerktumlare
3
如果你必须的话,或者在你已经掌握了Rust并且想要挑战自己(是的,挑战!这个很难)来构建这个数据结构之后,一定要阅读《用过多链表学习Rust》(https://rust-unofficial.github.io/too-many-lists/)。 - Chayim Friedman
1
不过,这是一篇很好的帖子,值得点赞,因为你做了研究。干得好! - Chayim Friedman
2个回答

3

你不需要。

如果值是Copy,你可以复制它。如果是Clone,你可以克隆它。但如果不是这样,或者克隆太昂贵?

你就没有办法了。

内部可变性(比如RefCell)对于API来说是不好的。它往往传播到API边界,并有时甚至完全阻止你实现它们。它在实现内部可能是好的(有时候),但是泄漏出去就不好了。

当然,有一种正确的方法来实现双向链表(因为std有一个),但这种正确的方法需要使用不安全的代码。当你已经精通Rust之后,你可以作为一项练习回头学习它。还要确保阅读Learn Rust With Entirely Too Many Linked Lists


1
这对我有用:
    pub fn get(&self, index: isize) -> Result<T, &'static str>
        where T: Clone
    {
        let mut current: Rc<RefCell<Node<T>>> = Rc::clone(self.head.borrow().next.as_ref().unwrap());
        for _ in 0..index {
            let tmp = Rc::clone(current.borrow().next.as_ref().ok_or("Index out of range")?);
            current = tmp;
        }
        let guard = current.borrow();
        guard.data.clone().ok_or("Index out of range")
    }

playground link
你需要添加where T: Clone这一行来启用.clone()方法。
有人说为了学习Rust而实现一个双向链表是个错误...很抱歉我要报告他们是对的。当我刚开始时,我也做了同样的事情。另外一件我尝试过的事情是编写一个光线追踪器;那个进展得更顺利。
核心数据结构使用原始指针来实现。这意味着需要编写不安全的代码,并在其周围提供一个安全的API:这是一项高级的Rust技能。

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