Why can't `&(?Sized + Trait)` be cast to `&dyn Trait`?

后端 未结 3 2176
死守一世寂寞
死守一世寂寞 2020-12-01 18:47

In the code below it is not possible to obtain a reference to a trait object from a reference to a dynamically-sized type implementing the same trait. Why is this the case?

3条回答
  •  孤城傲影
    2020-12-01 19:14

    Referenced from this blog, which explains the fat pointer really well.

    Thanks trentcl for simplifying the question to:

    trait Foo {}
    
    fn make_dyn(arg: &T) -> &dyn Foo {
        arg
    }
    

    This brings to how to cast between different ?Sized?

    To answer this, let's first peek the implementation for Unsized type Trait.

    trait Bar {
        fn bar_method(&self) {
            println!("this is bar");
        }
    }
    
    trait Foo: Bar {
        fn foo_method(&self) {
            println!("this is foo");
        }
    }
    
    impl Bar for u8 {}
    impl Foo for u8 {}
    
    fn main() {
        let x: u8 = 35;
        let foo: &dyn Foo = &x;
        // can I do
        // let bar: &dyn Bar = foo;
    }
    

    So, can you do let bar: &dyn Bar = foo;?

    // below is all pseudo code
    pub struct TraitObjectFoo {
        data: *mut (),
        vtable_ptr: &VTableFoo,
    }
    
    pub struct VTableFoo {
        layout: Layout,
        // destructor
        drop_in_place: unsafe fn(*mut ()),
        // methods shown in deterministic order
        foo_method: fn(*mut ()),
        bar_method: fn(*mut ()),
    }
    
    // fields contains Foo and Bar method addresses for u8 implementation
    static VTABLE_FOO_FOR_U8: VTableFoo = VTableFoo { ... };
    
    

    From the pseudo code, we can know

    // let foo: &dyn Foo = &x;
    let foo = TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8};
    // let bar: &dyn Bar = foo;
    // C++ syntax for contructor
    let bar = TraitObjectBar(TraitObjectFoo {&x, &VTABLE_FOO_FOR_U8});
    

    The bar type is TraitObjectBar, which is not the type TraitObjectFoo. That is to say, you cannot assign a struct of one type to another different type (in rust, in C++ you can use reinterpret_cast).

    What you can do it to have another level of indirection:

    impl Bar for dyn Foo {
    ...
    }
    
    let bar: &dyn Bar = &foo;
    // TraitObjectFoo {&foo, &VTABLE_FOO_FOR_DYN_FOO}
    

    The same thing applies to Slice.

    The workaround for casting different Unsized can be done by this trick:

    // blanket impl for all sized types, this allows for a very large majority of use-cases
    impl AsBar for T {
        fn as_bar(&self) -> &dyn Bar { self }
    }
    
    // a helper-trait to do the conversion
    trait AsBar {
        fn as_bar(&self) -> &dyn Bar;
    }
    
    // note that Bar requires `AsBar`, this is what allows you to call `as_bar`
    // from a trait object of something that requires `Bar` as a super-trait
    trait Bar: AsBar {
        fn bar_method(&self) {
            println!("this is bar");
        }
    }
    
    // no change here
    trait Foo: Bar {
        fn foo_method(&self) {
            println!("this is foo");
        }
    }
    

提交回复
热议问题