Generic struct that can hold either Rc<RefCell<T>> or Arc<RwLock<T>>

80 views Asked by At

I'm struggling with the implementing a struct in Rust that is able to hold either Rc<RefCell<T>> or Arc<RwLock<T>>.

The motivation of such a design is to allow the user to use cheaper and faster Rc<RefCell<T>> when the code will run in a single thread and only go to Arc<RwLock<T>> when parallelization is needed.

That's what I'm doing now:

struct Structure {}

struct Sel<R,DR> 
where
    R: Deref<Target=DR>,
    DR: Borrow<Structure>,
{
    structure: R,
}

impl<R,DR> Sel<R,DR> 
where
    R: Deref<Target=DR>,
    DR: Borrow<Structure>,
{
    fn new(structure: R) -> Self {
        Self{structure}
    }
}

fn main() {
    let st = Rc::new(RefCell::new(Structure{}));
    let sel = Sel::new(st);
}

The structure Sel can hold any type R that dereferences to whatever type DR, which in turn implements Borrow (Structure is the user type to wrap). In theory both Rc<RefCell<T>> and Arc<RwLock<T>> should satisfy these trait bounds, but the compiler shouts at me:

error[E0277]: the trait bound `RefCell<Structure>: Borrow<Structure>` is not satisfied
  --> src/main.rs:26:15
   |
26 |     let sel = Sel::new(st);
   |               ^^^^^^^^ the trait `Borrow<Structure>` is not implemented for `RefCell<Structure>`

What is the correct way of implementing this? Which trait bound I can use to express that DR could be used in place of &Structure?

1

There are 1 answers

0
yesint On BEST ANSWER

After some excellent hints in the comments I've found a solution, which appeared to be universal enough to justify creation of the crate for it. I've called it UniRcLock.

It is based on the idea to define a common trait for Rc<RefCell<T>> and Arc<RwLock<T>>. The implementation requires some advanced trait magic with generic associated types that allows to handle the lifetimes and to express the behavior of scoped read and write access guards in a generic manner.

It could be used like this (copy-pasted from the crate docs):

#[derive(Debug)]
struct Foo(i32);

fn incr_foo(v: impl UniRcLock<Foo>){
    v.write().0 += 1;
}

// Using Rc
let ptr1 = Rc::new(RefCell::new(Foo(0)));
incr_foo(ptr1.clone());
println!("After increment: {:?}", ptr1);

// Using Arc
let ptr2 = Arc::new(RwLock::new(Foo(0)));
incr_foo(ptr2.clone());
println!("After increment: {:?}", ptr2);

I hope it could be useful :)