正如您可能已经知道的那样,Rust中的类型可以是大小和不定大小的。不定大小的类型,顾名思义,没有存储此类型值所需的大小,这点对编译器来说是未知的。例如,
[u32]
是一个不定大小的
u32
数组;因为元素数量在任何地方都未指定,编译器也不知道它的大小。另一个例子是裸的 trait 对象类型,例如,
Display
,当它直接用作类型时。
let x: Display = ...;
在这种情况下,编译器不知道实际使用的类型是什么,它被擦除了,因此它不知道这些类型的值的大小。上面的代码行是无效的 -
你不能创建一个本地变量而不知道它的大小(为了在堆栈上分配足够的字节),并且你
不能将未定大小的类型的值作为参数传递给函数或从函数中返回。
然而,未定大小的类型可以通过指针来使用,这个指针可以携带额外的信息 - 切片可用数据的长度(
&[u32]
)或者虚表的指针(
Box<SomeTrait>
)。因为指针总是有固定和已知的大小,所以它们可以存储在本地变量中,并被传递到或从函数中返回。
对于任何具体的类型,您总是可以说它是否是有大小限制的或者没有大小限制的。但是,对于泛型类型,一个问题就出现了 - 某些类型参数是否有大小限制?
fn generic_fn<T>(x: T) -> T { ... }
如果
T
是无大小限制的,那么这个函数定义是不正确的,因为你不能直接传递无大小限制的值。如果有大小限制,那么一切都没问题。
在 Rust 中,所有泛型类型参数默认情况下都是有大小限制的 - 在函数、结构体和 trait 中都是如此。它们有一个隐式的
Sized
约束;
Sized
是用于标记有大小限制类型的 trait:
fn generic_fn<T: Sized>(x: T) -> T { ... }
这是因为在绝大多数情况下,您希望您的泛型参数具有大小。然而,有时您可能想取消大小性,并且可以通过?Sized
限定来实现:
fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }
现在可以像这样调用
generic_fn("abcde")
,并且
T
将被实例化为无大小的
str
,但这没关系-此函数接受对
T
的引用,因此不会发生任何问题。
然而,在Rust中,Trait总是针对某种类型实现的,这里大小的问题也很重要。
trait A {
fn do_something(&self);
}
struct X;
impl A for X {
fn do_something(&self) {}
}
然而,这只是为了方便和实用性而必要的。可以定义特征始终采用一个类型参数,并且不指定实现特征的类型:
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 的主体中可用:
trait A {
fn do_something(t: &Self);
}
这就涉及到大小问题。在Rust中,默认情况下,
Self
参数是非定长的。每个trait都对
Self
有一个隐含的
?Sized
限制。其中一个原因是因为有很多trait可以实现非定长类型并且仍然有效。例如,任何只包含通过引用获取和返回
Self
的方法的trait都可以实现非定长类型。你可以在
RFC 546中阅读更多关于动机的内容。
当你只定义trait及其方法的签名时,大小不是问题。由于这些定义中没有实际代码,编译器无法做出任何假设。但是,当你开始编写使用此trait的通用代码时,包括默认方法,因为它们带有隐含的
Self
参数,你应该考虑大小问题。由于默认情况下
Self
是非定长的,因此默认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)
}
}