为什么`Self`在编译时需要一个恒定的大小来调用一个自由函数,但是等效的trait函数却没有?

时间:2017-09-01 09:12:11

标签: rust traits

这两个函数,一个特征和一个免费,似乎是相似的,但调用其中一个(特征函数)是允许的,而调用另一个则不是:

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稳定)

1 个答案:

答案 0 :(得分:9)

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

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

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

问题归结为编译器如何表示事物。我们首先考虑Self类似Sized类型i32的情况。这意味着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是一个“胖”指针,包含两个指针的信息:指向基础数据的指针字符串的长度。当编译器创建ATraitObject时,它可以将ptr设置为self.as_ptr(),并且可以设置vtable ...但是它无处存储字符串的长度!

所有非Sized类型都有这个问题,因为它们都有指针中附带的额外信息。顺便说一句,这个包含特征对象,如&A,这意味着您也无法将特征对象转换为另一个特征对象,因为您现在需要两个的vtables。

这就是问题所在:编译器只是无法&Self Self: !Sized转变为&Aself中包含的信息太多,而且没有足够的空间将其存储在&A中。

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