您可能已经知道,Rust 中的类型可以调整大小和不调整大小。顾名思义,无大小类型没有存储编译器已知的这种类型值所需的大小。例如,[u32] 是u32s 的一个未调整大小的数组;因为元素的数量没有在任何地方指定,编译器不知道它的大小。另一个例子是裸 trait 对象类型,例如 Display,当它直接用作类型时:
let x: Display = ...;
在这种情况下,编译器不知道这里实际使用了哪种类型,它被擦除,因此它不知道这些类型的值的大小。上述行无效 - 您不能在不知道其大小的情况下创建局部变量(在堆栈上分配足够的字节),并且您不能传递未调整大小的值输入函数作为参数或从函数中返回。
可以通过指针使用未定型类型,但是,它可以携带附加信息 - 切片可用数据的长度 (&[u32]) 或指向虚拟表的指针 (Box<SomeTrait>)。因为指针始终具有固定且已知的大小,所以它们可以存储在局部变量中,并可以传递给函数或从函数返回。
给定任何具体类型,您总是可以说出它是否已调整大小。然而,对于泛型,出现了一个问题 - 某些类型参数是否有大小?
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)
}
}