How can I create an Rc<RefCell<_>> to something that already exists?

Viewed 38

So, in order to teach myself some Rust, I decided to create a simply hybrid datatype that I like, the ChainVec. It's a doubly linked list of vectors, useful if you need to resize vectors that could grow to vast scales. It uses linked list logic, but the lookup, search, and iterate characteristics are much faster because they grow O(n/alot). That's not really the point, though: The problem I ran into would occur in any doubly linked list.

Here's the problem... in C, when I make the new linked list node, I can just set its 'tail' to a pointer to the previous. I can toss pointers like candy in C, but in Rust, I don't know how to create new pointers yet. Here is what I tried... (look under fn push_head for the invalid line)

Note: In my implementation, the 'head' node to the chainvec is whichever you choose to point to, so there is no separate struct for the list head/tail.

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

#[allow(dead_code)]
struct ChainVec<T> {
    item: Option<Vec<T>>,
    head: Option<Rc<RefCell<ChainVec<T>>>>,
    tail: Option<Rc<RefCell<ChainVec<T>>>>,
}

impl<T> ChainVec<T> {
    fn new(prealloc: usize) -> Self {
        let vector: Vec<T> = Vec::with_capacity(prealloc);
        Self {
            item: Some(vector),
            head: None,
            tail: None,
        }
    }

    fn new_with_links(
        prealloc: usize,
        head: Option<Rc<RefCell<ChainVec<T>>>>,
        tail: Option<Rc<RefCell<ChainVec<T>>>>,
    ) -> Self {
        let vector: Vec<T> = Vec::with_capacity(prealloc);
        Self {
            item: Some(vector),
            head: head,
            tail: tail,
        }
    }
    fn push_head(&mut self, prealloc: usize) {
        self.head = Some(Rc::new(RefCell::new(ChainVec::new_with_links(
            prealloc,
            None,
            Some(Rc::new(RefCell::new(self))),
        ))));
    }
}

#[allow(unused_variables)]
fn main() {
    let alef: ChainVec<u32> = ChainVec::new(100);
    println!("Hello, world!");
}

Obviously I got lost when I tried to create a new pointer in push_head. I got:

error[E0308]: mismatched types
  --> src/main.rs:36:39
   |
36 |             Some(Rc::new(RefCell::new(self))),
   |                          ------------ ^^^^ expected struct `ChainVec`, found `&mut ChainVec<T>`
   |                          |
   |                          arguments to this function are incorrect
   |
   = note:         expected struct `ChainVec<T>`
           found mutable reference `&mut ChainVec<T>`

A dereference operator doesn't do anything here... and I'm not sure how to proceed.

How would I create an Rc<RefCell<_>> that references a struct that already exists?

1 Answers

Simple answer: You can't.

As soon as you are inside of a function that takes &self or &mut self as a parameter, the function has no knowledge about the fact that it is wrapped in an Rc. So you cannot create a new Rc from it.

If you could, how would the existing Rcs and the new Rc know how long to keep the object alive, and when to delete it?

You can only ever create a new Rc from an existing one. That means, if you want to achieve that, you need to take the existing Rc as a parameter instead of &self.


As an excurse on how other libraries deal with this:

tokio_util::sync::CancellationToken has a similar problem. Its solution is to hide the Rc in the .inner member, and then when operating on it, it passes the entire Arc to the handling method.

You can see this for example on the clone(), which does pretty much exactly what you are trying to do: it runs some code on it and creates a new Arc. You can see how it passes the entire Arc into the tree_node:: function; the tree_node mod is pretty much like the collection of member functions that operate on the Arc.

Related