为什么在这个特征中需要 `Sized` 界限?

Why is the `Sized` bound necessary in this trait?

我有一个具有两个相关功能的特征:

trait WithConstructor: Sized {
    fn new_with_param(param: usize) -> Self;

    fn new() -> Self {
        Self::new_with_param(0)
    }
}

为什么第二种方法 (new()) 的默认实现强制我将 Sized 绑定到类型上?我认为这是因为堆栈指针操作,但我不确定。

如果编译器需要知道在堆栈上分配内存的大小, 为什么下面的示例不需要 Sized 而不是 T

struct SimpleStruct<T> {
    field: T,
}

fn main() {
    let s = SimpleStruct { field: 0u32 };
}

让我们看看如果您使用未调整大小的类型执行此操作会发生什么。

new() 您的new_with_param(_)方法的结果移动到调用者。但是除非类型被调整大小,否则应该移动多少字节?我们根本无法知道。这就是移动语义需要 Sized 类型的原因。

注意:各种 Boxes 旨在为这个问题提供运行时服务。

您可能已经知道,Rust 中的类型可以调整大小和取消大小。 Unsized 类型,顾名思义,没有存储编译器已知的这种类型的值所需的大小。例如,[u32] 是一个未调整大小的 u32 数组;因为没有在任何地方指定元素的数量,所以编译器不知道它的大小。另一个例子是一个裸特征对象类型,例如Display,当它直接用作类型时:

let x: Display = ...;

在这种情况下,编译器不知道这里实际使用的是哪种类型,它被擦除,因此它不知道这些类型的值的大小。上面一行是无效的 - 你不能在不知道它的大小的情况下创建一个局部变量(在堆栈上分配足够的字节),你不能通过未调整类型的值作为参数传入函数或 return 它来自一个 .

可以通过指针使用未确定大小的类型,但是,它可以携带附加信息 - 切片可用数据的长度 (&[u32]) 或指向虚拟 table (Box<SomeTrait>).因为指针总是有一个固定且已知的大小,所以它们可以存储在局部变量中并被传递到函数或从函数中 returned。

给定任何具体类型,您总能说出它是大小合适的还是未大小合适的。然而,对于泛型,出现了一个问题——某些类型参数是否有大小?

fn generic_fn<T>(x: T) -> T { ... }

如果 T 未调整大小,那么这样的函数定义是不正确的,因为您不能直接传递未调整大小的值。如果大小合适就OK了。

在 Rust 中,所有泛型类型参数在任何地方都默认设置大小 - 在函数、结构和特征中。他们有一个隐含的 Sized 界限; Sized 是标记大小类型的特征:

fn generic_fn<T: Sized>(x: T) -> T { ... }

这是因为在绝大多数情况下您希望调整通用参数的大小。然而,有时你会想要选择退出大小,这可以通过 ?Sized bound:

来完成
fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }

现在 generic_fn 可以像 generic_fn("abcde") 一样被调用,并且 T 将使用 str 实例化,但没关系 - 此函数接受对T,所以没有什么不好的事情发生。

然而,还有一个地方大小问题很重要。 Rust 中的特征总是为某种类型实现:

trait A {
    fn do_something(&self);
}

struct X;
impl A for X {
    fn do_something(&self) {}
}

然而,这只是出于方便和实用的目的。可以将特征定义为始终采用一个类型参数而不指定特征实现的类型:

// this is not actual Rust but some Rust-like language

trait A<T> {
    fn do_something(t: &T);
}

struct X;
impl A<X> {
    fn do_something(t: &X) {}
}

这就是 Haskell 类型 类 的工作方式,事实上,这就是在较低级别的 Rust 中实际实现特征的方式。

Rust 中的每个特征都有一个隐式类型参数,称为 Self,它指定了实现该特征的类型。它总是在特征的主体中可用:

trait A {
    fn do_something(t: &Self);
}

这就是大小问题出现的地方。 Self 参数大小是否合适?

事实证明,不,Self在Rust中默认是没有大小的。每个特征在 Self 上都有一个隐式的 ?Sized 绑定。需要这样做的原因之一是因为有很多特性可以为未确定大小的类型实现并且仍然有效。例如,任何仅包含仅通过引用获取和 return Self 的方法的特征都可以为未确定大小的类型实现。您可以在 RFC 546.

中阅读更多关于动机的信息

当您只定义特征及其方法的签名时,大小不是问题。因为这些定义中没有实际代码,所以编译器不能假设任何东西。但是,当您开始编写使用此特征的通用代码时,其中包括默认方法,因为它们采用隐式 Self 参数,您应该考虑大小。因为Self默认是没有size的,所以默认的trait方法不能returnSelf取值或者取值作为参数。因此,您需要指定 Self 必须默认设置大小:

trait A: Sized { ... }

或者你可以指定一个方法只能在 Self 大小设置时调用:

trait WithConstructor {
    fn new_with_param(param: usize) -> Self;

    fn new() -> Self
    where
        Self: Sized,
    {
        Self::new_with_param(0)
    }
}