Returning a smart pointer reference to a struct cast as a trait object

Hi, I'm trying to return a reference to a smart pointer to a struct cast as a trait object:

struct Struct {}
trait Trait {}
impl Trait for Struct {}

struct Nest {
    s: Rc<Struct>,
}

impl Nest {
    fn inner_struct_as_trait(&self) -> &Rc<dyn Trait> {
        &self.s
    }
}

But I receive the error:

12 |     fn inner_struct_as_trait(&self) -> &Rc<dyn Trait> {
   |                                        -------------- expected `&Rc<(dyn Trait + 'static)>` because of return type
13 |         &self.s
   |         ^^^^^^^ expected trait object `dyn Trait`, found struct `Struct`
   |
   = note: expected reference `&Rc<(dyn Trait + 'static)>`
              found reference `&Rc<Struct>`

This seems like something that should be possible, but I'm not quite sure how to get rust to accept it.

As a side note, where does the + 'static for the trait object come from? Is this just specifying that the trait object has lifetime 'static or that the underlying struct which implements the trait has lifetime 'static? Does it have an impact on what I'm trying to accomplish?

See here.

The function signature you wrote is impossible to implement (without leaking a newly created Rc).

Turning concrete types into trait object types is an operation that always only can work behind exactly one level of indirection. You can turn an owned Rc<Struct> into Rc<dyn Trait> or a reference &Struct into &dyn Trait, but you cannot turn &Rc<Struct> into &Rc<dyn Trait> behind two levels of indirection.

This is because of where the dynamic information (vtable pointer) is stored:

Rc<Struct>: [ #address ]
               | (points to)
               |
               +--+
                  |
                  v
[reference count] [ Struct’s field values ]
Rc<dyn Trait>: [ #address, #vtable_ptr ]
                  |         |
                  |         +----------------+
                  v                          |
[reference count] [ Struct’s field values ]  |
                                             |
            +--------------------------------+
            |
            v
the vtable: [ size, destructor fn-pointer, fn-pointers for methods, … ] 

So you can turn Rc<Struct> into Rc<dyn Struct> by adding the vtable pointer. Same for works references

&Struct: [ #address ]
            |
+-----------+
|
v
[ Struct’s field values ]
&dyn Trait: [ #address, #vtable_ptr ]
               |         |
+--------------+         |
|                        +--+
v                           |
[ Struct’s field values ]   |
                            |
            +---------------+
            |
            v
the vtable: [ size, destructor fn-pointer, fn-pointers for methods, … ] 

However, with two indirections

&Rc<Struct>: [ #address ]
                |
            +---+
            |
            v
Rc<Struct>: [ #address ]
               |
               |
               +--+
                  |
                  v
[reference count] [ Struct’s field values ]
&Rc<dyn Trait>: [ #address ]
                   |
               +---+
               |
               v
Rc<dyn Trait>: [ #address, #vtable_ptr ]
                  |         |
                  |         +----------------+
                  v                          |
[reference count] [ Struct’s field values ]  |
                                             |
            +--------------------------------+
            |
            v
the vtable: [ size, destructor fn-pointer, fn-pointers for methods, … ] 

you can see how turning &Rc<Struct> into &Rc<dyn Trait> can not work, because there’s no preexisting Rc<dyn Trait> anywhere in memory to point to, and the existing Rc<Struct> doesn’t have the vtable pointer in it. The &Rc<dyn Trait> you’d like to return cannot own such that Rc<dyn Trait> either, since references don’t have ownership.

The only things that do work for your function signature is either to return an owned rc Rc<dyn Trait> by cloning the Rc from the field, or a direct reference to the trait object &dyn Trait. Which one you want depends on your use-case.

6 Likes

This was incredibly detailed and easy-to-understand. Thank you. I appreciate it.

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.