您可能已经知道,Rust 中的类型可以调整大小和取消大小。顾名思义,无大小类型没有编译器已知的存储该类型值所需的大小。例如,[u32]
是一个未调整大小的数组u32
s;因为元素的数量没有在任何地方指定,所以编译器不知道它的大小。另一个例子是裸特征对象类型,例如,Display
,当它直接用作类型时:
let x: Display = ...;
在这种情况下,编译器不知道这里实际使用的是哪种类型,它被删除,因此它不知道这些类型的值的大小。上面的行无效 -如果不知道局部变量的大小,就无法创建局部变量(在堆栈上分配足够的字节),并且您无法将未调整大小的类型的值作为参数传递给函数或从函数返回它.
然而,可以通过指针使用未指定大小的类型,该指针可以携带附加信息 - 切片的可用数据的长度(&[u32]
)或指向虚拟表的指针(Box<SomeTrait>
)。由于指针始终具有固定且已知的大小,因此它们可以存储在局部变量中并传递到函数或从函数返回。
给定任何具体类型,您始终可以判断它是否已调整大小或未调整大小。然而,对于泛型,出现了一个问题 - 某些类型参数是否具有大小?
fn generic_fn<T>(x: T) -> T { ... }
If T
未调整大小,那么这样的函数定义是不正确的,因为您不能直接传递未调整大小的值。如果尺寸合适,那么一切就OK了。
在 Rust 中,所有泛型类型参数的大小都默认在任何地方 - 在函数、结构和特征中。他们有一个隐含的Sized
边界;Sized
是标记大小类型的特征:
fn generic_fn<T: Sized>(x: T) -> T { ... }
这是因为在绝大多数情况下,您希望调整通用参数的大小。然而,有时您想要选择退出尺寸,这可以通过?Sized
bound:
fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }
Now generic_fn
可以这样称呼generic_fn("abcde")
, and 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 中默认没有大小。每个特质都有一个隐含的?Sized
绑定于Self
。需要这样做的原因之一是因为有很多特征可以针对未调整大小的类型实现并且仍然有效。例如,任何仅包含仅接受和返回的方法的特征Self
通过引用可以针对未确定大小的类型实现。您可以阅读有关动机的更多信息RFC 546 https://github.com/rust-lang/rfcs/blob/master/text/0546-Self-not-sized-by-default.md.
当您只定义特征及其方法的签名时,大小不是问题。由于这些定义中没有实际代码,因此编译器无法假设任何内容。但是,当您开始编写使用此特征的通用代码时,其中包括默认方法,因为它们采用隐式方法Self
参数,您应该考虑尺寸。因为Self
默认情况下没有调整大小,默认特征方法无法返回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)
}
}