为什么 `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) {}
我本以为在这两种情况下我都是通过一个指针访问的,其大小 是 在编译时已知,那么这种行为的区别和解释是什么?
(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
转换为 &A
。 self
中包含的信息太多,space 不足以将其存储在 &A
.
中
要获取要编译的方法,请将 where Self: Sized
子句添加到方法定义中。这向编译器保证该方法永远不会在非 Sized
类型上调用,因此可以自由假设从 &Self
到 &A
的转换总是可能的。
这两个函数,一个 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
是一个完全不同的海洋生物烹饪容器。
问题归结为编译器如何表示事物。让我们首先考虑 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
转换为 &A
。 self
中包含的信息太多,space 不足以将其存储在 &A
.
要获取要编译的方法,请将 where Self: Sized
子句添加到方法定义中。这向编译器保证该方法永远不会在非 Sized
类型上调用,因此可以自由假设从 &Self
到 &A
的转换总是可能的。