How do I implement an iterator from a vector of std::Rc<std::RefCell<T>> smart pointers?

506 Views Asked by At

I'm trying to understand how to work with interior mutability. This question is strongly related to my previous question.

I have a generic struct Port<T> that owns a Vec<T>. We can "chain" port B to port A so, when reading the content of port A, we are able to read the content of port B. However, this chaining is hidden to port A's reader. That is why I implemented the iter(&self) method:

use std::rc::Rc;

pub struct Port<T> {
    values: Vec<T>,
    ports: Vec<Rc<Port<T>>>,
}

impl <T> Port<T> {
    pub fn new() -> Self {
        Self { values: vec![], ports: vec![] }
    }

    pub fn add_value(&mut self, value: T) {
        self.values.push(value);
    }

    pub fn is_empty(&self) -> bool {
        self.values.is_empty() && self.ports.is_empty()
    }

    pub fn chain_port(&mut self, port: Rc<Port<T>>) {
        if !port.is_empty() {
            self.ports.push(port)
        }
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.values.iter().chain(
            self.ports.iter()
                .flat_map(|p| Box::new(p.iter()) as Box<dyn Iterator<Item = &T>>)
        )
    }

    pub fn clear(&mut self) {
        self.values.clear();
        self.ports.clear();
    }
}

The application has the following pseudo-code behavior:

  • create ports
  • loop:
    • fill ports with values
    • chain ports
    • iterate over ports' values
    • clear ports

The main function should look like this:

fn main() {
    let mut port_a = Rc::new(Port::new());
    let mut port_b = Rc::new(Port::new());

    loop {
        port_a.add_value(1);
        port_b.add_value(2);

        port_a.chain_port(port_b.clone());

        for val in port_a.iter() {
            // read data
        };

        port_a.clear();
        port_b.clear();
    }
}

However, the compiler complains:

error[E0596]: cannot borrow data in an `Rc` as mutable
  --> src/modeling/port.rs:46:9
   |
46 |         port_a.add_value(1);
   |         ^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<Port<i32>>`

I've been reading several posts etc., and it seems that I need to work with Rc<RefCell<Port<T>>> to be able to mutate the ports. I changed the implementation of Port<T>:

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

pub struct Port<T> {
    values: Vec<T>,
    ports: Vec<Rc<RefCell<Port<T>>>>,
}

impl<T> Port<T> {

    // snip

    pub fn chain_port(&mut self, port: Rc<RefCell<Port<T>>>) {
        if !port.borrow().is_empty() {
            self.ports.push(port)
        }
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.values.iter().chain(
            self.ports
                .iter()
                .flat_map(|p| Box::new(p.borrow().iter()) as Box<dyn Iterator<Item = &T>>),
        )
    }

    // snip
}

This does not compile either:

error[E0515]: cannot return value referencing temporary value
  --> src/modeling/port.rs:35:31
   |
35 |                 .flat_map(|p| Box::new(p.borrow().iter()) as Box<dyn Iterator<Item = &T>>),
   |                               ^^^^^^^^^----------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |                               |        |
   |                               |        temporary value created here
   |                               returns a value referencing data owned by the current function

I think I know what the problem is: p.borrow() returns a reference to the port being chained. We use that reference to create the iterator, but as soon as the function is done, the reference goes out of scope and the iterator is no longer valid.

I have no clue on how to deal with this. I managed to implement the following unsafe method:

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        self.values.iter().chain(self.ports.iter().flat_map(|p| {
            Box::new(unsafe { (&*p.as_ref().as_ptr()).iter() }) as Box<dyn Iterator<Item = &T>>
        }))
    }

While this works, it uses unsafe code, and there must be a safe workaround.

I set a playground for more details of my application. The application compiles and outputs the expected result (but uses unsafe code).

1

There are 1 best solutions below

5
On

You can't modify anything behind an Rc, that's correct. While this might be solved with a RefCell, you don't want to go down that road. You might come into a situation where you'd need to enforce a specific clean() order or similar horrors.

More important: your main is fundamentally flawed, ownership-wise. Take these lines:

let mut port_a = Port::new();
let mut port_b = Port::new();

loop {
    // Creates an iummutable borrow of port_b with same lifetime as port_a!
    port_a.chain_port(port_b);

    // ...

    // A mutable borrow of port_b.
    // But the immutable borrow from above persists across iterations.
    port_b.clear();
    // Or, even if you do fancy shenanigans at least until this line.
    port_a.clear();
}

To overcome this, just constrain the ports lifetime to one iteration. You currently manually clean them up anyway, so that's already what you're doing conceptually.

Also, I got rid of that recursive iteration, just to simplify things a little more.

#[derive(Clone)]
pub struct Port<'a, T> {
    values: Vec<T>,
    ports: Vec<&'a Port<'a, T>>,
}

impl<'a, T> Port<'a, T> {
    pub fn new() -> Self {
        Self {
            values: vec![],
            ports: vec![],
        }
    }

    pub fn add_value(&mut self, value: T) {
        self.values.push(value);
    }

    pub fn is_empty(&self) -> bool {
        self.values.is_empty() && self.ports.is_empty()
    }

    pub fn chain_port(&mut self, port: &'a Port<T>) {
        if !port.is_empty() {
            self.ports.push(&port)
        }
    }

    pub fn iter(&self) -> impl Iterator<Item = &T> {
        let mut port_stack: Vec<&Port<T>> = vec![self];

        // Sensible estimate I guess.
        let mut values: Vec<&T> = Vec::with_capacity(self.values.len() * (self.ports.len() + 1));

        while let Some(port) = port_stack.pop() {
            values.append(&mut port.values.iter().collect());
            port_stack.extend(port.ports.iter());
        }

        values.into_iter()
    }
}

fn main() {
    loop {
        let mut port_a = Port::new();
        let mut port_b = Port::new();

        port_a.add_value(1);
        port_b.add_value(2);

        port_a.chain_port(&port_b);
 
        print!("values in port_a: [ ");
        for val in port_a.iter() {
            print!("{} ", val);
        }
        println!("]");
    }
}