为什么 `Self` 在编译时需要一个常量大小来调用自由函数,而等效的 trait 函数却不需要?

Why does `Self` require a constant size at compile time for calling a free function but the equivalent trait function does not?

这两个函数,一个 trait 和一个 free,看起来很相似,但是允许调用其中一个(trait 函数),而不允许调用另一个:

trait A {
    fn foo(&self) {
        bar(self);    // 1. Error: `Self` does not have a constant size known at compile-time
        A::bar(self); // 2. This works
    }

    fn bar(&self) {}
}

fn bar(_a: &A) {}

(Playground link for above)

我本以为在这两种情况下我都是通过一个指针访问的,其大小 在编译时已知,那么这种行为的区别和解释是什么?

(Rust 1.19 稳定)

因为两人想要的东西完全不同

A::foo里面self的类型是&Self不是&A。也就是说,它是指向实现类型的指针,而不是指向特征对象的指针。调用 A::bar 很好,因为它只是传递所述指针,不需要额外的工作。

召唤::bar是一个完全不同的海洋生物烹饪容器。

问题归结为编译器如何表示事物。让我们首先考虑 Self 是像 i32 这样的 Sized 类型的情况。这意味着 self&i32。回想一下 &A 是一个特征对象,因此 实际上 具有以下布局:

struct ATraitObject {
    ptr: *const (),
    vtable: *const AVtable,
}

ATraitObject.ptr 需要指向实际值本身,ATraitObject.vtable 需要指向该类型的特征的实现。编译器可以用 ptr 作为现有的 self 指针来填充它,并且 vtable 填充指向内存中 impl A for i32 vtable 所在位置的指针。这样,它就可以调用 ::bar.

现在考虑 Self 而不是 Sized 的情况,例如 str。这意味着 self 是一个 "fat" 指针,并且包含两个指针值的信息:指向基础数据的指针 字符串的长度。当编译器去创建 ATraitObject 时,它可以将 ptr 设置为 self.as_ptr(),它可以设置 vtable... 但是它没有地方存储字符串的长度!

所有非Sized类型都有这个问题,因为它们的指针中都有额外的信息。顺便说一句,这个 包括 特征对象,如 &A,这意味着你也不能将特征对象变成 另一个 特征对象,因为您现在需要两个 vtable。

这就是问题所在:编译器 无法 &Self 其中 Self: !Sized 转换为 &Aself 中包含的信息太多,space 不足以将其存储在 &A.


要获取要编译的方法,请将 where Self: Sized 子句添加到方法定义中。这向编译器保证该方法永远不会在非 Sized 类型上调用,因此可以自由假设从 &Self&A 的转换总是可能的。