Rc<dyn trait> as a struct member

lets take for example this piece of code.

trait Thingy {
    fn func1(self: &Rc<Self>);
}

struct ImplementThingy{}

impl Thingy for ImplementThingy {
    fn func1(self: &Rc<Self>) {
        println!("ImplementThingy func1!");
    }
}

fn main() {
    let hello = Rc::new(ImplementThingy {});
    hello.func1();
}

This prints the ImplementThingy thingy.
However when I add

struct HasPointer {
    member:Rc<dyn Thingy>
}

impl HasPointer {
    fn new(x: u32) -> HasPointer {
        
        let member: Rc<dyn Thingy> = if x > 10 {
            Rc::new(ImplementThingy {})
            
        } else {
            Rc::new(ImplementThingy2 {})
        };
        
        return Self {
            member
        };
    }
}

The code does not compile anymore:

   Compiling playground v0.0.1 (/playground)
error[E0038]: the trait `Thingy` cannot be made into an object
  --> src/main.rs:4:15
   |
4  |     member:Rc<dyn Thingy>
   |               ^^^^^^^^^^ `Thingy` cannot be made into an object
...
24 |     fn func1(self: &Rc<Self>);
   |                    --------- help: consider changing method `func1`'s `self` parameter to be `&self`: `&Self`
   |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
  --> src/main.rs:24:20
   |
23 | trait Thingy {
   |       ------ this trait cannot be made into an object...
24 |     fn func1(self: &Rc<Self>);
   |                    ^^^^^^^^^ ...because method `func1`'s `self` parameter cannot be dispatched on

For more information about this error, try `rustc --explain E0038`.
error: could not compile `playground` (bin "playground") due to previous error

and to make both pieces of code work I need to remove the & from the fn func1(self: &Rc<Self>) .

I was wondering if there's a way that

fn main() {
    let hello = Rc::new(ImplementThingy {});
    hello.func1();
    
    let x = HasPointer::new(7);
    x.member.func1();
    x.member.func1();
}

would work.
I don't understand why adding that dyn rc member causes the code to stop working.

The compiler knows how to coerce the wide pointer of an Rc<dyn Trait> to a thin pointer of an Rc<SomeUnknownErasedType> in order to do the method dispatching, but it doesn't know how to coerce &Rc<dyn Trait> (and/or it would be unsound to do so behind a reference?).

There are no nested coercions for dispatch, similar to how there are no nested coercions for unsizing. [1] That is, you can't coerce &Rc<ImplementThingy> to &Rc<dyn Thingy> either.

This is the relevant trait.


  1. Except for Pin which is problematic... ↩ī¸Ž

So what should I do instead?
I don't want to do .clone() every time I use the member

I'm not sure I have enough context. If you never need to clone in the method, take &self. If it's more complicated and you sometimes need to clone... an example closer to your actual use case would be useful.

(Cloning an Rc might be cheaper than you think; you might want to try and get something you can performance compare to see if further complication is worth it.)

1 Like

I want in my real application to create a struct that represents an allocator.
I want that allocator to have an allocate memory.
I want that allocate function to return an Allocation structure that has an Rc to the allocator.

I want the allocator struct to stay alive until all Allocation structures that have a reference to it all drop.
In the Drop of Allocation I call the free function of the Allocator

struct allocation {
    address: u64,
    allocator: rc<dyn allocator>,
}
impl drop for allocation  {
    fn drop(&mut self) -> {
        self.allocator.free(self)
    }
}


trait allocator {
    fn alloc(self: &Rc<Self>) -> Allocation,
    fn free(self: &Rc<Self>, allocation: &Allocation)
}

I want something like this to work

The problem is that Rc<dyn Trait> and Rc<ConcreteType> are two distinct types that have different memory layouts. When you coerce from one to the other, the compiler destroys one and constructs the other.

You can't coerce behind a reference because there's no place to store the coerced-into type, and therefore nowhere to point the reference to.

so in regards to this what do you think is the correct way to do that?

You could not create (store) it at all, but instead change the outer reference (effectively to a wide pointer) to be a reference to a thin pointer. Might even be a no-op depending on layout.

There could still be something I'm not thinking of though (and I doubt this is a thing that will happen anyway, so moot).

Maybe this?

Or maybe this?

I took your code and played with it a bit

but it does not look like ThingyA functions gets called

In the second example it doesn't. I don't understand your overall design though.

Maybe this? But I'm just guessing.

Don't you think this design is too convoluted?

It's my stab at something that might fit into your unspecified requirements based on what informal back and forth there has been.

It's your project, so whether it's too convoluted or not is your call. Feel free to use it or not.

Mmmm.
First of all thanks for the help :slight_smile:
It's just that the code got real ugly real quick with those changes. It's like there is just an unnecessary extra level of abstraction.
It just seems like there is a bug or something because without the self: &Rc<Self> and with self: Rc<Self> the code does work but just requires and extra .clone() for each function invocation (because of the move).
I just want a way to pass it by reference in the self argument that does not require adding ton of code and complicated stuff like <dyn Allocator>::alloc_bare(&**self, size)

I prefer to be explicit with recursive implementations, but that could just be (**self).alloc_bare(size). It is a lot of boilerplate but it's "write it once and actually using it is ergonomic" type boilerplate.


Anyway, let me a step back; maybe I got too distracted with iteratively changing things. Correct me if I'm wrong, but the crux of the situation is:

trait WishThisWasObjectSafe {
    // Needing to clone the Rc<_> but not wanting to pass it by cloning
    fn boo_alloc(self: &Rc<Self>) -> Rc<dyn WishThisWasObjectSafe>;
    fn boo_free(self: &Rc<Self>, other: ());
}

And I'm deriving here that you always want the Rc<dyn ...> that is returned to be the same shared object all the time, so you don't care about the cloning with alloc per se -- you need to do it inside the method anyway -- you just care about the ergonomics of calling alloc.

That is,

let allocator: Rc<dyn Allocator> = ...;
// You don't care about cloning but this is too onerous to type
let allocation = allocator.clone().alloc();
// You want this
let allocation = allocator.alloc();

Let me further assume you don't need an Rc specifically in free and that could be

    fn boo_free(&self, other: ());

If that's all accurate, I think there is a better way.


Start with something that is object safe (albeit not the call signature you want).

trait ThisOneIsObjectSafe {
    fn yay_alloc(self: Rc<Self>) -> Rc<dyn ThisOneIsObjectSafe>;
    fn yay_free(&self, other: ());
}

That's what allocator types will actually implement.

struct SomeoneElse;
impl ThisOneIsObjectSafe for SomeoneElse {
    fn yay_alloc(self: Rc<Self>) -> Rc<dyn ThisOneIsObjectSafe> {
        self
    }
    fn yay_free(&self, _other: ()) {}
}

Then put the interface you actually want on a different trait.

trait MakeItErgonomic {
    fn alloc(&self) -> Rc<dyn ThisOneIsObjectSafe>;
    fn free(&self, other: ());
}

impl<T: ThisOneIsObjectSafe + ?Sized> MakeItErgonomic for Rc<T> {
    fn alloc(&self) -> Rc<dyn ThisOneIsObjectSafe> {
        self.clone().yay_alloc()
    }
    fn free(&self, other: ()) {
        self.yay_free(other);
    }
}

Or as applied to one of the prior playgrounds.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.