如果我有这个代码:
trait Trait {
fn f(&self) -> i32 where Self: Sized;
fn g(&self) -> i32;
}
fn object_safety_dynamic(x: &Trait) {
x.f(); // error
x.g(); // works
}
where
条款实际上做了什么?
天真的,我在想where Self: Sized;
决定实施Trait
的类型,如'如果你为Trait
类型实现A
你的类型A
必须大小,即,它可以是i32
但不是[i32]
。
但是,这样的约束更像是trait Trait: Sized
(如果我错了,请纠正我)?
现在我注意到where Self: Sized;
实际上确定我是否可以从f
中调用g
或object_safety_dynamic
。
我的问题:
where Self: Sized;
告诉编译器使g()
工作但f()
不工作?&self
无论如何都是参考,f
和g
之间存在各种(大小或未大小)类型的编译差异。它不会总是归结为像_vtable_f_or_g(*self) -> i32
,无论where
或类型是否大小?u8
和[u8]
实现Trait。不应该编译器实际上阻止我为f()
实现[u8]
,而不是在调用站点抛出错误?where子句实际上做了什么?
天真,我在想自我:大小;指示实现Trait的类型,比如'如果你为A类实现Trait你的类型A必须是大小的,即它可以是i32而不是[i32]。
然而,这种约束宁愿作为特质Trait:Sized
这是对的。
但是,在这种情况下,绑定仅适用于该函数。仅在呼叫站点检查where
功能界限。
幕后发生了什么?
关于rust的语法有一个令人困惑的地方,Trait
可以参考
Trait
;要么Trait
,实际上是一种类型,而不是一个对象。Sized
是一种特性,任何类型的T
都是Sized
,它的大小可以用std::mem::size_of::<T>()
作为常数。这种类型不大的是str
和[u8]
,其内容没有固定的大小。
类型Trait
也没有。直觉上,这是因为Trait
作为一个类型由实现特征Trait
的所有类型值组成,它们可能具有不同的大小。这意味着你永远不会有Trait
类型的值 - 你只能通过“胖指针”来引用一个,例如&Trait
或Box<Trait>
等等。它们的大小为2个指针 - 一个用于vtable,一个用于数据。看起来大致如下:
struct &Trait {
pub data: *mut (),
pub vtable: *mut (),
}
自动形式的impl:
impl Trait /* the trait */ for Trait /* the type */ {
fn f(&self) -> i32 where Self: Sized { .. }
fn g(&self) -> i32 {
/* vtable magic: something like (self.vtable.g)(self.data) */
}
}
什么(用简单的英语)我实际上告诉编译器Self:Sized;这使得g()工作但f()没有?
请注意,因为,正如我所提到的,Trait
不是Sized
,绑定的Self: Sized
不满意所以函数f
不能被称为Self == Trait
。
特别是:由于&self无论如何都是参考,对于各种(大小或未大小)类型,f和g之间存在编译差异。它不会总是归结为像_vtable_f_or_g(* self) - > i32这样的东西,无论在何处或类型是否大小?
Trait
类型始终未标注。哪种类型被胁迫到Trait
并不重要。使用Sized
变量调用函数的方法是直接使用它:
fn generic<T: Trait + Sized>(x: &T) { // the `Sized` bound is implicit, added here for clarity
x.f(); // compiles just fine
x.g();
}
为什么我可以为u8和[u8]实现Trait。难道编译器实际上不应该阻止我为[u8]实现f(),而不是在调用站点抛出错误?
因为这个特性不受Self: Sized
的限制 - 函数f
是。所以没有什么可以阻止你实现这个功能 - 只是函数的界限永远不会被满足,所以你永远不能调用它。
fn f(&self) -> i32 where Self: Sized;
这表示f
仅针对也实现Sized
的类型定义。 Unsized类型仍然可以实现Trait
,但f
将无法使用。
在object_safety_dynamic
内部,调用x.f()
实际上是在做:(*x).f()
。虽然x
的大小是因为它是一个指针,但*x
可能不是因为它可能是Trait
的任何实现。但函数内部的代码必须适用于任何有效参数,因此不允许在那里调用x.f()
。