Why is `let ref a: Trait = Struct` forbidden?

前端 未结 1 575
遥遥无期
遥遥无期 2021-01-14 01:18

We have a noncopyable type and a trait:

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

If we create a &Struct and

相关标签:
1条回答
  • 2021-01-14 01:59

    There's a little bit of unsized subtlety going on here. The key difference between

    let a: &Struct = &Struct;
    let ref a: Struct = *a;
    

    and

    let a: &Trait = &Struct;
    let ref a: Trait = *a;
    

    Is that the expression *a produces a value whose size is not known at compile-time. This manifests as an error when we attempt to do:

    let ref a: Trait = Struct as Trait;
    
    <anon>:6:24: 6:39 error: cast to unsized type: `Struct` as `Trait`
    <anon>:6     let ref a: Trait = Struct as Trait;
                                    ^~~~~~~~~~~~~~~
    <anon>:6:24: 6:30 help: consider using a box or reference as appropriate
    <anon>:6     let ref a: Trait = Struct as Trait;
    

    In general, the compiler can't know the size of a bare trait used as a type, like Trait is used here. This is because any type can implement Trait - so the size of trait can be any size, depending on the type that implements it. So, that explains why let ref a: Trait = Struct and let a: &Struct = &Struct; let ref a: Trait = *a don't work, because casting a Struct to a Trait is an unsized cast.

    As for why your working trait code snippet works, looking at the MIR for these two examples, we can see that the compiler is treating the two above assignments slightly differently:

    let a: &Struct = &Struct;
    let ref a: Struct = *a;
    
    bb0: {
        tmp1 = Struct;
        tmp0 = &tmp1;
        var0 = &(*tmp0);
        var1 = &(*var0);
        return = ();
        goto -> bb1;
    }
    
    let a: &Trait = &Struct;
    let ref a: Trait = *a;
    
    bb0: {
        tmp2 = Struct;
        tmp1 = &tmp2;
        tmp0 = &(*tmp1);
        var0 = tmp0 as &'static Trait + 'static (Unsize);
        var1 = &(*var0);
        return = ();
        goto -> bb1;
    }
    

    We see that the compiler has to do a cast to a trait object &'static Trait + 'static to satisfy the implicit coercion of &Struct to &Trait. From there, the ref pattern is simply var1 = &(*var0);, which in this case is a simple assignment from the trait object var0 to the trait object var1.

    This is similar to the MIR generated by this function:

    fn stuff() {
        let sized = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
        let slice : &[u8] = &sized;
        let ref other_slice = *slice;
    }
    
    bb0: {
        var0 = [const 1u8, ..., const 0u8];
        tmp2 = &var0;
        tmp1 = &(*tmp2);
        var1 = tmp1 as &'static [u8] (Unsize);
        var2 = &(*var1);
        return = ();
        goto -> bb1;
    }
    

    Since the type [u8] is unsized, it does a similar cast to a slice, which is quite similar in layout to a trait object. Ultimately, the compiler allows the code that doesn't introduce any unsized locals.

    0 讨论(0)
提交回复
热议问题