为什么在这个 trait 中需要 `Sized` 边界? [英] Why is the `Sized` bound necessary in this trait?

查看:83
本文介绍了为什么在这个 trait 中需要 `Sized` 边界?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个带有两个关联函数的特征:

I have a trait with two associated functions:

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

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

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

Why does the default implementation of the second method (new()) force me to put the Sized bound on the type? I think it's because of the stack pointer manipulation, but I'm not sure.

如果编译器需要知道在栈上分配内存的大小,为什么以下示例不需要 TSized ?

If the compiler needs to know the size to allocate memory on the stack, why does the following example not require Sized for T?

struct SimpleStruct<T> {
    field: T,
}

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

推荐答案

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

As you probably already know, types in Rust can be sized and unsized. Unsized types, as their name suggests, do not have a size required to store values of this type which is known to the compiler. For example, [u32] is an unsized array of u32s; because the number of elements is not specified anywhere, the compiler doesn't know its size. Another example is a bare trait object type, for example, Display, when it is used directly as a type:

let x: Display = ...;

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

In this case, the compiler does not know which type is actually used here, it is erased, therefore it does not know the size of values of these types. The above line is not valid - you can't make a local variable without knowing its size (to allocate enough bytes on the stack), and you can't pass the value of an unsized type into a function as an argument or return it from one.

但是,可以通过指针使用未调整大小的类型,该指针可以携带附加信息 - 切片的可用数据长度(&[u32])或指向虚拟表的指针(Box).由于指针始终具有固定且已知的大小,因此它们可以存储在局部变量中,并可以传入或从函数中返回.

Unsized types can be used through a pointer, however, which can carry additional information - the length of available data for slices (&[u32]) or a pointer to a virtual table (Box<SomeTrait>). Because pointers always have a fixed and known size, they can be stored in local variables and be passed into or returned from functions.

给定任何具体类型,您总是可以说它是有尺寸的还是无尺寸的.然而,对于泛型,出现了一个问题 - 某些类型参数是否具有大小?

Given any concrete type you can always say whether it is sized or unsized. With generics, however, a question arises - is some type parameter sized or not?

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

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

If T is unsized, then such a function definition is incorrect, as you can't pass unsized values around directly. If it is sized, then all is OK.

在 Rust 中,所有泛型类型参数在任何地方都是默认大小的——在函数、结构和特征中.它们有一个隐式的 Sized 绑定;Sized 是标记大小类型的特征:

In Rust all generic type parameters are sized by default everywhere - in functions, in structs and in traits. They have an implicit Sized bound; Sized is a trait for marking sized types:

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

这是因为在绝大多数情况下,您都希望调整通用参数的大小.但是,有时您希望选择退出大小,这可以通过 ?Sized 绑定来完成:

This is because in the overwhelming number of times you want your generic parameters to be sized. Sometimes, however, you'd want to opt-out of sizedness, and this can be done with ?Sized bound:

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

现在generic_fn可以像generic_fn("abcde")那样调用,T将用str实例化> 这是未调整大小的,但没关系 - 此函数接受对 T 的引用,因此不会发生任何不好的事情.

Now generic_fn can be called like generic_fn("abcde"), and T will be instantiated with str which is unsized, but that's OK - this function accepts a reference to T, so nothing bad happens.

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

However, there is another place where question of sizedness is important. Traits in Rust are always implemented for some type:

trait A {
    fn do_something(&self);
}

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

然而,这只是为了方便和实用的目的.可以定义 trait 以始终​​采用一个类型参数而不指定 trait 实现的类型:

However, this is only necessary for convenience and practicality purposes. It is possible to define traits to always take one type parameter and to not specify the type the trait is implemented for:

// 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 在较低级别实际实现特征的方式.

That's how Haskell type classes work, and, in fact, that's how traits are actually implemented in Rust at a lower level.

Rust 中的每个 trait 都有一个隐式类型参数,称为 Self,它指定了这个 trait 实现的类型.它始终在 trait 的主体中可用:

Each trait in Rust has an implicit type parameter, called Self, which designates the type this trait is implemented for. It is always available in the body of the trait:

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

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

This is where the question of sizedness comes into the picture. Is the Self parameter sized?

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

It turns out that no, Self is not sized by default in Rust. Each trait has an implicit ?Sized bound on Self. One of the reasons this is needed because there are a lot of traits which can be implemented for unsized types and still work. For example, any trait which only contains methods which only take and return Self by reference can be implemented for unsized types. You can read more about motivation in RFC 546.

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

Sizedness is not an issue when you only define the signature of the trait and its methods. Because there is no actual code in these definitions, the compiler can't assume anything. However, when you start writing generic code which uses this trait, which includes default methods because they take an implicit Self parameter, you should take sizedness into account. Because Self is not sized by default, default trait methods can't return Self by value or take it as a parameter by value. Consequently, you either need to specify that Self must be sized by default:

trait A: Sized { ... }

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

or you can specify that a method can only be called if Self is sized:

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

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

这篇关于为什么在这个 trait 中需要 `Sized` 边界?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆