Заимствованный RefCell не длится достаточно долго при итерации по списку - PullRequest
3 голосов
/ 25 марта 2019

Я пытаюсь реализовать связанный список, чтобы понять умные указатели в 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

Что случилось?Разве мы не можем использовать ссылку для итерации на узле, определенном таким образом?

1 Ответ

2 голосов
/ 25 марта 2019

Вместо присвоения 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() удаляется (т.е. освобождается, выделяется) после итерации цикла, но вы пытаетесь использовать его члены в следующем цикле (это называется use after free, и одна из целей разработки 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, но я не смог обойти проблемы с жизненным циклом.

...