How do I pass Rc<RefCell<Box<MyStruct>>> to a function accepting Rc<RefCell<Box<dyn MyTrait>>>?
Asked Answered
A

1

9

I have originally asked this question here, but it was marked as duplicate, although it duplicates only a part of it in my opinion, so I have created a more specific one:

Consider the following code:

use std::rc::Rc;

trait MyTrait {
    fn trait_func(&self);
}

struct MyStruct1;

impl MyStruct1 {
    fn my_fn(&self) {
        // do something
    }
}

impl MyTrait for MyStruct1 {
    fn trait_func(&self) {
        // do something
    }
}

fn my_trait_fn(t: Rc<dyn MyTrait>) {
    t.trait_func();
}

fn main() {
    let my_str: Rc<MyStruct1> = Rc::new(MyStruct1);
    my_trait_fn(my_str.clone());
    my_str.my_fn();
}

This code works fine. Now I want to change the definition of trait_func to accept a &mut self, but it won't work as Rc works with immutable data only. The solution I use is to wrap MyTrait into RefCell:

use std::cell::RefCell;

fn my_trait_fn(t: Rc<RefCell<Box<dyn MyTrait>>>) {
    t.borrow_mut().trait_func();
}

fn main() {
    let my_str: Rc<RefCell<Box<MyStruct1>>> = Rc::new(RefCell::new(Box::new(MyStruct1)));
    my_trait_fn(my_str.clone());
    my_str.my_fn();
}

When I compile it I get an error:

error[E0308]: mismatched types
  --> src/main.rs:27:17
   |
27 |     my_trait_fn(my_str.clone());
   |                 ^^^^^^^^^^^^^^ expected trait MyTrait, found struct `MyStruct1`
   |
   = note: expected type `std::rc::Rc<std::cell::RefCell<std::boxed::Box<dyn MyTrait + 'static>>>`
              found type `std::rc::Rc<std::cell::RefCell<std::boxed::Box<MyStruct1>>>`
   = help: here are some functions which might fulfill your needs:
           - .into_inner()

What's the best way to go around this problem?

Acceleration answered 16/6, 2015 at 7:26 Comment(2)
note: your initial code only works on nightly due to the Rc<MyTrait>, your second code uses an additional Box which is only necessary on stable. Without the Box your code works fine on nightly with just some minor modifications: is.gd/zQUJqp . Please edit your question if you want a solution for stable rust.Hydrolyse
Thanks. I do indeed work with nightly (sorry, should have mentioned). Removing the Box seems to be the solution to my problem. I think the ability for RefCell to work directly with Trait was introduced recently that's why it wasn't working for me originally (when I updated my nightly Rust about 2 weeks ago).Acceleration
D
5

(An older revision of this answer essentially advised to clone the underlying struct and put it in a new Rc<RefCell<Box<MyTrait>> object; this was necessary at the time on stable Rust, but since not long after that time, Rc<RefCell<MyStruct>> will coerce to Rc<RefCell<MyTrait>> without trouble.)

Drop the Box<> wrapping, and you can coerce Rc<RefCell<MyStruct>> to Rc<RefCell<MyTrait>> freely and easily. Recalling that cloning an Rc<T> just produces another Rc<T>, increasing the refcount by one, you can do something like this:

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

trait MyTrait {
    fn trait_func(&self);
}

#[derive(Clone)]
struct MyStruct1;
impl MyStruct1 {
    fn my_fn(&self) {
        // do something
    }
}

impl MyTrait for MyStruct1 {
    fn trait_func(&self) {
        // do something
    }
}

fn my_trait_fn(t: Rc<RefCell<MyTrait>>) {
    t.borrow_mut().trait_func();
}

fn main() {
    // (The type annotation is not necessary here, but helps explain it.
    // If the `my_str.borrow().my_fn()` line was missing, it would actually
    // be of type Rc<RefCell<MyTrait>> instead of Rc<RefCell<MyStruct1>>,
    // essentially doing the coercion one step earlier.)
    let my_str: Rc<RefCell<MyStruct1>> = Rc::new(RefCell::new(MyStruct1));
    my_trait_fn(my_str.clone());
    my_str.borrow().my_fn();
}

As a general rule, see if you can make things take the contained value by reference, ideally even generically—fn my_trait_fn<T: MyTrait>(t: &T) and similar, which can typically be called as my_str.borrow() with automatic referencing and dereferencing taking care of the rest—rather than the whole Rc<RefCell<MyTrait>> thing.

Dumbstruck answered 16/6, 2015 at 7:55 Comment(7)
coercing a Rc<T> to Rc<Trait> for T: Trait is possible on nightly... I don't think he wanted to copy the actual object, otherwise there's no reason for Rc at all... Your last paragraph is imo the answer... Don't pass the Rc<RefCell<Box<X>>> madness, but simply call borrow and pass a referenceHydrolyse
@ker: yes, Rc coercion is possible and safe (and it was necessary, Rc::new clearly can’t work for an unsized T), because it only exposes an immutable reference; Rc<RefCell<T>> will never be coercible to Rc<RefCell<Trait>>, because RefCell allows internal mutability, which could allow putting an object of a different type and size in, which would cause the Rc to refer to different objects simultaneously. It will thus always need to clone the entire value.Dumbstruck
Nope, as I said in my comment to the question, Rc<RefCell<T>> -> Rc<RefCell<Trait>> is fine on nightly and I don't really see how it could be an issue, since you cannot create a Rc<RefCell<Trait>> except through coercion.Hydrolyse
since we cannot modify a trait object in safe code, RefCell<Trait> can never be changed to point to another object that implements the same trait, because we only get a &mut Trait, which doesn't allow changing the trait object, only the original object that implemented the trait.Hydrolyse
Somehow I always seem to forget that &mut Trait doesn’t allow replacement of the underlying object even when my statements skirt around why it wouldn’t make sense… sighDumbstruck
Thanks both. As mentioned in my previous comment - removing Box is the right solution and works on nightly only for now.Acceleration
I wonder why it's so big difference between stack-based and heap-based structs that casting RefCell of stack struct works, but casting RefCell of boxed struct blames. This shortcoming really narrows the range of possibilities of creation of reusable code, especially when it is big enough to oversize the stack.Fretted

© 2022 - 2024 — McMap. All rights reserved.