Rust - Sized还是?Sized
动态大小类型 DST
读者大大们之前学过的几乎所有类型,都是固定大小的类型,包括集合 Vec
、String
和 HashMap
等,而动态大小类型刚好与之相反:编译器无法在编译期得知该类型值的大小,只有到了程序运行时,才能动态获知。对于动态类型,我们使用 DST
(dynamically sized types)或者 unsized
类型来称呼它。
上述的这些集合虽然底层数据可动态变化,感觉像是动态大小的类型。但是实际上,这些底层数据只是保存在堆上,在栈中还存有一个引用类型,该引用包含了集合的内存地址、元素数目、分配空间信息,通过这些信息,编译器对于该集合的实际大小了若指掌,最最重要的是:栈上的引用类型是固定大小的,因此它们依然是固定大小的类型。
正因为编译器无法在编译期获知类型大小,若你试图在代码中直接使用 DST 类型,将无法通过编译。
Sized 特征
在使用泛型时,以下泛型函数:
fn generic<T>(t: T) {
// --snip--
}
编译器自动帮我们加上了 Sized
特征约束:
fn generic<T: Sized>(t: T) {
// --snip--
}
在上面,Rust 自动添加的特征约束 T: Sized
,表示泛型函数只能用于一切实现了 Sized
特征的类型上,而所有在编译时就能知道其大小的类型,都会自动实现 Sized
特征。
每一个特征都是一个可以通过名称来引用的动态大小类型。因此如果想把特征作为具体的类型来传递给函数,你必须将其转换成一个特征对象:诸如 &dyn Trait
或者 Box<dyn Trait>
(还有 Rc<dyn Trait>
)这些引用类型。
现在还有一个问题:假如想在泛型函数中使用动态数据类型怎么办?可以使用 ?Sized
特征(不得不说这个命名方式很 Rusty,竟然有点幽默):
fn generic<T: ?Sized>(t: &T) {
// --snip--
}
?Sized
特征用于表明类型 T
既有可能是固定大小的类型,也可能是动态大小的类型。还有一点要注意的是,函数参数类型从 T
变成了 &T
,因为 T
可能是动态大小的,因此需要用一个固定大小的指针(引用)来包裹它。