为什么禁止`let ref a: Trait = Struct`?

oli*_*obk 4 rust

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

struct Struct;
trait Trait {}
impl Trait for Struct {}
Run Code Online (Sandbox Code Playgroud)

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

let a: &Struct = &Struct;
let ref a: Struct = *a;
Run Code Online (Sandbox Code Playgroud)

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

let ref a: Struct = Struct;
Run Code Online (Sandbox Code Playgroud)

但是如果我们声明我们的变量绑定需要引用,只有第一个代码片段有效

let a: &Trait = &Struct;
let ref a: Trait = *a;
Run Code Online (Sandbox Code Playgroud)

尝试直接执行此操作

let ref a: Trait = Struct;
Run Code Online (Sandbox Code Playgroud)

或者通过循环

let a: &Struct = &Struct;
let ref a: Trait = *a;
Run Code Online (Sandbox Code Playgroud)

或者

let ref a: Trait = *&Struct;
Run Code Online (Sandbox Code Playgroud)

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

这是根本没有实现(还没有?)还是有更深层次的原因被禁止?

小智 5

这里有一些未调整的微妙之处。之间的主要区别

let a: &Struct = &Struct;
let ref a: Struct = *a;
Run Code Online (Sandbox Code Playgroud)

let a: &Trait = &Struct;
let ref a: Trait = *a;
Run Code Online (Sandbox Code Playgroud)

是不是表达式*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;
Run Code Online (Sandbox Code Playgroud)

通常,编译器无法知道用作类型的裸特征的大小,就像Trait这里使用的那样。这是因为任何类型都可以实现Trait- 因此 trait 的大小可以是任意大小,具体取决于实现它的类型。所以,这就解释了为什么let ref a: Trait = Structlet a: &Struct = &Struct; let ref a: Trait = *a不起作用,因为将 a 转换Struct为 aTrait是一个未调整大小的转换。

至于为什么您的工作特征代码片段有效,查看这两个示例的 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;
}
Run Code Online (Sandbox Code Playgroud)

我们看到编译器必须对trait 对象 &'static Trait + 'static进行强制转换以满足 to 的隐式强制&Struct转换&Trait。从那里, ref 模式很简单var1 = &(*var0);,在这种情况下,它是从 trait objectvar0到 trait object的简单赋值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;
}
Run Code Online (Sandbox Code Playgroud)

由于类型[u8]未调整大小,因此它对slice 进行了类似的转换,这在布局上与 trait 对象非常相似。最终,编译器允许不引入任何未确定大小的局部变量的代码。