【问题标题】:Why is `let ref a: Trait = Struct` forbidden?为什么禁止`let ref a: Trait = Struct`?
【发布时间】:2018-06-02 08:09:26
【问题描述】:

我们有一个不可复制的类型和一个特征:

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

如果我们创建一个&Struct 并取消引用它,我们会得到一个右值引用,我们可以使用它来初始化一个按引用绑定:

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

我们也可以直接通过 ref 绑定来初始化:

let ref a: Struct = Struct;

但是如果我们声明我们的变量绑定需要引用,那么只有第一个代码 sn-p 有效

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

尝试直接这样做

let ref a: Trait = Struct;

或者通过循环

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

或者

let ref a: Trait = *&Struct;

会给我们一个mismatched types 错误。显然它们不是同一类型,但推断适用于引用。

这只是没有实现(还没有?)还是有更深层次的原因不被允许?

【问题讨论】:

    标签: rust


    【解决方案1】:

    这里有一点不为人知的微妙之处。

    之间的主要区别
    let a: &Struct = &Struct;
    let ref a: Struct = *a;
    

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

    是不是表达式*a 产生了一个在编译时大小未知的值。当我们尝试这样做时,这表现为错误:

    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;
    

    一般而言,编译器无法知道用作类型的裸特征的大小,例如此处使用Trait。这是因为 any type 可以实现 Trait - 所以 trait 的大小可以是 any size,具体取决于实现它的类型。所以,这就解释了为什么 let ref a: Trait = Structlet a: &amp;Struct = &amp;Struct; let ref a: Trait = *a 不起作用,因为将 Struct 转换为 Trait 是一个未调整大小的转换。

    至于为什么您的工作特征代码 sn-p 有效,查看这两个示例的 MIR,我们可以看到编译器对上述两个赋值的处理略有不同:

    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;
    }
    

    我们看到编译器必须对 trait 对象 &amp;'static Trait + 'static 进行强制转换,以满足 &amp;Struct&amp;Trait 的隐式强制转换。从那里开始,引用模式就是 var1 = &amp;(*var0);,在这种情况下,它是从 trait 对象 var0 到 trait 对象 var1 的简单赋值。

    这类似于这个函数生成的MIR:

    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;
    }
    

    由于[u8] 类型没有大小,它与slice 进行类似的强制转换,这在布局上与特征对象非常相似。最终,编译器允许代码不会引入任何未调整大小的局部变量。

    【讨论】:

    • 如何获得 MIR 输出??
    • 所以基本上这意味着 rustc 不能取消右值引用的大小,而只能取消实际引用。 @PiotrZolnierek:play.rust-lang.org 上有一个 MIR 按钮
    猜你喜欢
    • 1970-01-01
    • 2014-11-29
    • 1970-01-01
    • 1970-01-01
    • 2015-04-10
    • 2014-10-19
    • 2014-06-18
    • 2018-06-22
    • 1970-01-01
    相关资源
    最近更新 更多