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

Posted

技术标签:

【中文标题】为啥在这个特征中需要 `Sized` 界限?【英文标题】:Why is the `Sized` bound necessary in this trait?为什么在这个特征中需要 `Sized` 界限? 【发布时间】:2015-09-05 10:59:13 【问题描述】:

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

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 ;

【问题讨论】:

【参考方案1】:

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

let x: Display = ...;

在这种情况下,编译器不知道这里实际使用了哪种类型,它被擦除,因此它不知道这些类型的值的大小。上述行无效 - 您不能在不知道其大小的情况下创建局部变量(在堆栈上分配足够的字节),并且您不能传递未调整大小的值输入函数作为参数或从函数中返回

可以通过指针使用未定型类型,但是,它可以携带附加信息 - 切片可用数据的长度 (&amp;[u32]) 或指向虚拟表的指针 (Box&lt;SomeTrait&gt;)。因为指针始终具有固定且已知的大小,所以它们可以存储在局部变量中,并可以传递给函数或从函数返回。

给定任何具体类型,您总是可以说出它是否已调整大小。然而,对于泛型,出现了一个问题 - 某些类型参数是否有大小?

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

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

在 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) 

但是,这只是为了方便和实用的目的。可以将 trait 定义为始终采用一个类型参数,并且不指定实现 trait 的类型:

// 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 中的每个 trait 都有一个隐式类型参数,称为 Self,它指定了实现此 trait 的类型。它始终在特征的主体中可用:

trait A 
    fn do_something(t: &Self);

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

事实证明,不,Self 在 Rust 中默认没有大小。每个特征都有一个隐含的?Sized 绑定在Self 上。需要这样做的原因之一是因为有很多特征可以为未调整的类型实现并且仍然有效。例如,任何仅包含仅通过引用获取和返回 Self 的方法的 trait 都可以为 unsized 类型实现。你可以在RFC 546阅读更多关于动机的信息。

当您只定义特征及其方法的签名时,大小不是问题。因为这些定义中没有实际的代码,所以编译器不能假设任何东西。但是,当您开始编写使用此特征的通用代码时,其中包括默认方法,因为它们采用隐式 Self 参数,您应该考虑到大小。因为Self默认没有size,所以默认的trait方法不能按值返回Self或者按值作为参数。因此,您要么需要指定 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)
    

【讨论】:

感谢您提供如此完整的答案。我不知道所有“默认情况下是 Sized 但 Self 不是”部分。这就是我感到困惑的主要原因。 @Vladimir 不幸的是,Advanced Traits 和 Advanced Types Rust Book 的章节已经被冻结了。否则你绝对应该考虑在那里提出你的解释【参考方案2】:

让我们看看如果你对一个未调整大小的类型执行此操作会发生什么。

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

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

【讨论】:

为什么它不抱怨new_with_param呢?它还需要在调用者的堆栈上保留适量的空间。 所以我的想法是正确的,但是为什么泛型结构中不需要Size?我更新了问题。 @Matthieu M. new_with_param 只是一个特征方法定义,而不是一个实现。 @AndreaP:struct 默认始终为Sized 我想我明白了。显然,泛型类型T(不是结构)在默认情况下被视为结构的大小(除非您输入?Sized),而不是特征。 doc.rust-lang.org/book/unsized-types.html

以上是关于为啥在这个特征中需要 `Sized` 界限?的主要内容,如果未能解决你的问题,请参考以下文章

“方法存在但不满足特征界限”是啥意思?

方法存在但不满足以下特征界限(泛型)

为啥我要在特征上实现方法而不是作为特征的一部分?

为啥用子集选择方法在这个数据集中选择的特征太少

为啥我们需要一个从粗到细的策略来解决实践中的光流问题(特征跟踪)?

“我的第一次生锈”:枚举 `Option<&mut ...>` 存在方法 `unwrap_or_default`,但不满足其特征界限