特征中的 '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.

中调用 fg

我的问题:

  1. 幕后发生了什么?

  2. 我实际上通过 where Self: Sized; 告诉编译器什么(用简单的英语)使 g() 工作但 f() 不工作?

  3. 特别是:由于 &self 无论如何都是一个引用,对于各种(大小或未大小)类型,fg 之间存在编译差异。它不会总是归结为 _vtable_f_or_g(*self) -> i32 之类的东西,而不管 where 或者类型是否有大小?

  4. 为什么我可以同时为 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 可能不是因为它可能是 Traitany 实现。但是函数内部的代码必须适用于任何有效参数,因此您不能在那里调用 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 是一个特征,任何 TSized 的类型都可以将其大小作为常量,由 std::mem::size_of::<T>()。这种没有大小的类型是 str[u8],它们的内容没有固定大小。

类型 Trait 也未调整大小。直觉上,这是因为 Trait 作为一个类型由实现特征 Trait 的所有类型的值组成,这些值可能具有不同的大小。这意味着您永远不能拥有 Trait 类型的值——您只能通过“胖指针”(例如 &TraitBox<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 是。所以没有什么能阻止你实现这个函数——只是函数的边界永远无法满足,所以你永远不能调用它。