这两个函数,一个特征和一个免费,似乎是相似的,但调用其中一个(特征函数)是允许的,而调用另一个则不是:
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) {}
我想在两种情况下我都是通过在编译时知道 大小的指针来访问的,那么这种行为的区别和解释是什么?
(Rust 1.19稳定)
答案 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
转变为&A
。 self
中包含的信息太多,而且没有足够的空间将其存储在&A
中。
要获取编译方法,请在方法定义中添加where Self: Sized
子句。这保证了编译器永远不会在非Sized
类型上调用该方法,因此可以自由地假设从&Self
到&A
的转换始终是可能的。