这两个函数,一个 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) {}
我本以为在这两种情况下,我都是通过一个在编译时已知大小 的指针进行访问,那么这种行为有什么区别和解释?
(Rust 1.19 稳定版)
最佳答案
因为两者想要的东西完全不同。
A::foo
中self
的类型是&Self
,不是 &A
。也就是说,它是指向实现类型的指针,而不是指向特征对象的指针。调用 A::bar
很好,因为它只是传递所述指针,不需要额外的工作。
调用 ::bar
是一个完全不同的海洋生物 cooking 容器。
问题归结为编译器如何表示事物。让我们首先考虑 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
是一个“胖”指针,包含两个指针的信息值(value):指向基础数据的指针 和 字符串的长度。当编译器创建ATraitObject
时,它可以将ptr
设置为self.as_ptr()
,并且可以设置vtable
...但是它没有地方可以存储字符串的长度!
所有非Sized
类型都有这个问题,因为它们的指针中都有额外的信息。顺便说一句,这包括特征对象,如&A
,这意味着你也不能将特征对象变成另一个特征对象,因为你现在需要两个虚表。
这就是问题所在:编译器根本没有办法将 &Self
where Self: !Sized
转换为 &A
。 self
中包含的信息太多,而 &A
中没有足够的空间来存储它。
要获得要编译的方法,请将 where Self: Sized
子句添加到方法定义中。这向编译器保证该方法永远不会在非 Sized
类型上调用,因此可以自由地假设从 &Self
到 &A
的转换> 总是可能的。
关于rust - 为什么 `Self` 在编译时需要一个常量大小来调用自由函数,而等效的特征函数却不需要?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45996828/