特征中的 'where' 子句有什么作用?
What does the 'where' clause within a trait do?
如果我有这个代码:
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
的类型,比如“如果你为类型 A
实现 Trait
你的类型 A
必须调整大小,即它可以是 i32
但不能是 [i32]
.
然而,这样的约束更像是trait Trait: Sized
(如果我错了请纠正我)?
现在我注意到 where Self: Sized;
实际上决定了我是否可以从 object_safety_dynamic
.
中调用 f
或 g
我的问题:
幕后发生了什么?
我实际上通过 where Self: Sized;
告诉编译器什么(用简单的英语)使 g()
工作但 f()
不工作?
特别是:由于 &self
无论如何都是一个引用,对于各种(大小或未大小)类型,f
和 g
之间存在编译差异。它不会总是归结为 _vtable_f_or_g(*self) -> i32
之类的东西,而不管 where
或者类型是否有大小?
为什么我可以同时为 u8
和 [u8]
实现 Trait。编译器真的不应该阻止我为 [u8]
实现 f()
,而不是在调用站点抛出错误吗?
fn f(&self) -> i32 where Self: Sized;
这表示 f
仅针对也实现了 Sized
的类型定义。未调整大小的类型可能仍会实现 Trait
,但 f
将不可用。
在object_safety_dynamic
里面,调用x.f()
实际上是在做:(*x).f()
。虽然 x
的大小是因为它是一个指针,但 *x
可能不是因为它可能是 Trait
的 any 实现。但是函数内部的代码必须适用于任何有效参数,因此您不能在那里调用 x.f()
。
What does the where clause actually do?
Naively, I was thinking where Self: Sized; dictates something about the type implementing Trait, like 'if you implement Trait for type A your type A must be sized, i.e., it can be i32 but not [i32].
However, such a constraint would rather go as trait Trait: Sized
这是正确的。
但是,在这种情况下,绑定仅适用于函数。 where
仅在调用点检查函数边界。
What happens here behind the scenes?
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 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) */
}
}
What (in simple English) am I actually telling the compiler by where Self: Sized; that makes g() work but f() not?
请注意,正如我提到的,由于 Trait
不是 Sized
,绑定 Self: Sized
不满足,因此无法调用函数 f
Self == Trait
.
In particular: Since &self is a reference anyway, what compiled difference exists between f and g for various (sized or unsized) types. Wouldn't it always boil down to something like _vtable_f_or_g(*self) -> i32, regardless of where or if the type is sized or not?
类型 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();
}
Why can I implement Trait for both u8 and [u8]. Shouldn't the compiler actually stop me from implementing f() for [u8], instead of throwing an error at the call site?
因为特征不受 Self: Sized
的限制 - 函数 f
是。所以没有什么能阻止你实现这个函数——只是函数的边界永远无法满足,所以你永远不能调用它。
如果我有这个代码:
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
的类型,比如“如果你为类型 A
实现 Trait
你的类型 A
必须调整大小,即它可以是 i32
但不能是 [i32]
.
然而,这样的约束更像是trait Trait: Sized
(如果我错了请纠正我)?
现在我注意到 where Self: Sized;
实际上决定了我是否可以从 object_safety_dynamic
.
f
或 g
我的问题:
幕后发生了什么?
我实际上通过
where Self: Sized;
告诉编译器什么(用简单的英语)使g()
工作但f()
不工作?特别是:由于
&self
无论如何都是一个引用,对于各种(大小或未大小)类型,f
和g
之间存在编译差异。它不会总是归结为_vtable_f_or_g(*self) -> i32
之类的东西,而不管where
或者类型是否有大小?为什么我可以同时为
u8
和[u8]
实现 Trait。编译器真的不应该阻止我为[u8]
实现f()
,而不是在调用站点抛出错误吗?
fn f(&self) -> i32 where Self: Sized;
这表示 f
仅针对也实现了 Sized
的类型定义。未调整大小的类型可能仍会实现 Trait
,但 f
将不可用。
在object_safety_dynamic
里面,调用x.f()
实际上是在做:(*x).f()
。虽然 x
的大小是因为它是一个指针,但 *x
可能不是因为它可能是 Trait
的 any 实现。但是函数内部的代码必须适用于任何有效参数,因此您不能在那里调用 x.f()
。
What does the where clause actually do?
Naively, I was thinking where Self: Sized; dictates something about the type implementing Trait, like 'if you implement Trait for type A your type A must be sized, i.e., it can be i32 but not [i32].
However, such a constraint would rather go as trait Trait: Sized
这是正确的。
但是,在这种情况下,绑定仅适用于函数。 where
仅在调用点检查函数边界。
What happens here behind the scenes?
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 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) */
}
}
What (in simple English) am I actually telling the compiler by where Self: Sized; that makes g() work but f() not?
请注意,正如我提到的,由于 Trait
不是 Sized
,绑定 Self: Sized
不满足,因此无法调用函数 f
Self == Trait
.
In particular: Since &self is a reference anyway, what compiled difference exists between f and g for various (sized or unsized) types. Wouldn't it always boil down to something like _vtable_f_or_g(*self) -> i32, regardless of where or if the type is sized or not?
类型 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();
}
Why can I implement Trait for both u8 and [u8]. Shouldn't the compiler actually stop me from implementing f() for [u8], instead of throwing an error at the call site?
因为特征不受 Self: Sized
的限制 - 函数 f
是。所以没有什么能阻止你实现这个函数——只是函数的边界永远无法满足,所以你永远不能调用它。