Rc<RefCell<_>> as self

My main concern here is using Rc<RefCell<_>> as self, but the compiler disallows that.
But rather than that, I'm concerned if I'm doing something reasonable.

The real-world task is more complicated, but I need a container for items, and items have to know which container they are in. Also, I decided container should not own these items, cause they are already owned by another structure in the code.

Given that I need several references for my objects and they all have to be modifiable I decided I have to use Rc<RefCell<_>>.

Here is the example code, that tries to model this.

I really do not like FooContainer::add(&container... instead of container.add(....
But also, I feel this code is too complicated for this type of task.

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

#[derive(Debug)]
struct Foo {
    container_im_in: Option<Weak<RefCell<FooContainer>>>,
}

impl Foo {
    fn new() -> Rc<RefCell<Foo>> {
        Rc::new(RefCell::new(Foo {
            container_im_in: None,
        }))
    }
}

#[derive(Debug)]
struct FooContainer {
    vec: Vec<Weak<RefCell<Foo>>>,
}

impl FooContainer {
    fn new() -> Rc<RefCell<FooContainer>> {
        Rc::new(RefCell::new(FooContainer { vec: Vec::new() }))
    }

	fn add(self_: &Rc<RefCell<Self>>, foo: &Rc<RefCell<Foo>>) {
		assert!(foo.borrow().container_im_in.is_none());

		self_.borrow_mut().vec.push(Rc::downgrade(foo));
		foo.borrow_mut().container_im_in = Some(Rc::downgrade(self_));
	}

	fn remove(self_: &Rc<RefCell<Self>>, foo: &Rc<RefCell<Foo>>) {
		
		let prev_container = foo.borrow_mut().container_im_in.take().unwrap(); 
		assert!(Weak::ptr_eq(&Rc::downgrade(self_), &prev_container));

		let vec = &mut self_.borrow_mut().vec;
		let foo_weak = Rc::downgrade(foo);
		let pos = vec.iter().position(|it| Weak::ptr_eq(it, &foo_weak)).unwrap();
		vec.remove(pos);
	}
}

fn main() {
	let foo1 = Foo::new();
	let foo2 = Foo::new();

	let container = FooContainer::new();

	FooContainer::add(&container, &foo1);
	dbg!(&container);
	FooContainer::add(&container, &foo2);
	dbg!(&container);
	FooContainer::remove(&container, &foo1);
	dbg!(&container);
	FooContainer::remove(&container, &foo2);
	dbg!(&container);
}

using Rc<RefCell<_>> as self, but the compiler disallows that.

If a type is ubiquitously used with Rc<RefCell<..., you can consider putting the RefCell inside the type, leaving only the Rc outside. This will allow method calls to be used. Compilable sample:

use std::cell::RefCell;
use std::rc::{Rc, Weak};
struct Foo;

struct FooContainer(RefCell<FooContainerInner>);
struct FooContainerInner {
    vec: Vec<Weak<RefCell<Foo>>>,
}

impl FooContainer {
    fn new() -> Rc<Self> {
        Rc::new(FooContainer(RefCell::new(FooContainerInner { vec: Vec::new() })))
    }
    
	fn add(self: &Rc<Self>, foo: &Rc<RefCell<Foo>>) {
        todo!();
    }
}

The disadvantage — or possibly benefit — is that now the methods must decide what to do with RefCell borrow failures rather than letting the caller decide.

(I have not reviewed any of the rest of your question about whether this is a good idea.)

2 Likes