26、Sized trait
Sized trait
事实上,&str
远比表面上看起来更复杂,即使我们已经深入探讨过 Deref
强制转换机制之后也是如此。
根据我们之前对内存布局
的讨论,人们可能会合理地预期 &str
在栈上应当只以一个 usize
(即指针)的形式存在。然而实际上并非如此。&str
除了保存指向字符串数据的指针之外,还额外存储了一些元数据:它所指向的字符串 slice 的长度。回到前一节的例子中:
let mut s = String::with_capacity(5);
s.push_str("Hello");
// Create a string slice reference from the `String`,
// skipping the first byte.
let slice: &str = &s[1..];
我们可以得到内存情况的图示:
这是怎么回事?
动态大小类型
str
是一个动态大小类型(dynamically sized type(DST))
DST是一个在编译时不确定其大小的类型,当我们对DST有一个引用时,比如&str
,它必须引入额外的关于它指向的数据的信息。这就是胖指针
在&str
的情况下,它存储它指向的切片的长度。我们之后可以看到更多的DST示例。
The Sized trait
Rust的标准库定义了叫做Sized
的trait。
pub trait Sized {
// This is an empty trait, no methods to implement.
}
编译时已知大小的数据类型就是Sized
。换句话说Sized
类型不是DST。
Marker trait
Sized
是我们第一个marker trait
的例子。
marker trait
是一种不需要任何方法实现的trait。他没有任何行为。它仅用于标记一个类型具有某些特征。然后编译器根据这些标记启用某些行为或优化。
Auto trait
特别的,Sized
也是一个Auto trait
。
我们不需要去显式地实现它,编译器会基于类型定义自动地为我们去实现它。
例子
我们之前看到的所有类型都是Sized类型:u32,String,bool等等。
str不是Sized
&str是Sized
我一开始还挺纳闷的,为什么String会是Sized呢?我猜这里应该特指栈上的数据,栈上的数据大小已知就是Sized,应该是说结构体这一块,字符串字面量的内存空间大小不也是已知的吗,所以我感觉怪怪的。问了一下AI,它又说&str没有实现Sized,奇怪了。之后我再研究一下!
哦哦,明白了,&str确实实现了Sized,他的字段是固定的。问题在于&str接受字符串字面量的时候,字符串字面量并不是存储在栈上,而是存储在静态存储的区域,&str到底它还是引用,它指向哪里是它的事情,它可以指向堆上的切片,也可以指向字符串字面量,大概就是这个意思吧。
练习
这次的练习并没有让我们做题,而是告诉了我们一件事情
size_of
方法,是获取栈上分配的内存大小的,适用于实现了Sized的类型,str没有实现Sized类型,于是报错,这个代码不合法!
更准确一些的解释:
std::mem::size_of
要求其类型参数必须是 Sized
的,因为它需要在编译期确定类型大小。而 str
是一个动态大小类型(DST),其大小在编译时未知,因此不实现 Sized
。所以 size_of::<str>()
会导致编译错误。正确的做法是使用 &str
来获取指向字符串的引用的大小(如 size_of::<&str>()
),但这也仅表示指针和长度的结构体大小(通常为 16 或 24 字节),不代表字符串内容的实际长度。
确实,加了&可以正常通过,但是这段代码似乎也没有什么意义吧?
这一节的学习就到这里了。