Sized
特性深入探讨
即使在研究了 deref
强制转换之后,&str
仍然有着更多不为人知的细节。根据我们之前关于内存布局的讨论,将 &str
表示为堆栈上的单一 usize
(一个指针)似乎是合理的。然而事实并非如此。&str
在指针旁边存储了一些元数据:它所指向切片的长度。回顾前一节中的例子:
#![allow(unused)] fn main() { let mut s = String::with_capacity(5); s.push_str("Hello"); // 从 `String` 创建一个字符串切片引用,跳过第一个字节。 let slice: &str = &s[1..]; }
在内存中,我们得到如下布局:
s slice
+---------+--------+----------+ +---------+--------+
Stack | pointer | length | capacity | | pointer | length |
| | | 5 | 5 | | | | 4 |
+----|----+--------+----------+ +----|----+--------+
| s |
| |
v |
+---+---+---+---+---+ |
Heap: | H | e | l | l | o | |
+---+---+---+---+---+ |
^ |
| |
+--------------------------------+
这是怎么回事?
动态大小类型
str
是一个动态大小类型(Dynamically Sized Type, DST)。DST是一种其大小在编译时未知的类型。每当你拥有一个指向DST的引用,比如&str
时,它必须包含额外的关于所指数据的信息。它是一个胖指针。对于&str
而言,它存储了所指向切片的长度。在课程的其余部分中,我们将看到更多DST的例子。
Sized
特性
Rust的标准库定义了一个叫做 Sized
的特性。
#![allow(unused)] fn main() { pub trait Sized { // 这是一个空特性,无需实现任何方法。} }
如果一个类型的大小在编译时已知,则它是 Sized
的。换句话说,它不是DST。
标记号特质
Sized
是你遇到的第一个标记特质的例子。标记特质不需要实现任何方法。它不定义任何行为。它仅用于标记类型具有某些属性。
这个标记随后被编译器利用,以启用特定行为或优化。
自动特质
特别地,Sized
也是一个自动特质。你不需要显式实现它;编译器会根据类型的定义自动为你实现。
示例
迄今为止我们见过的所有类型都是 Sized
的:u32
、String
、bool
等。
正如我们刚看到的,str
不是 Sized
的。
然而,&str
是 Sized
!我们在编译时知道它的大小:两个 usize
,一个用于指针,一个用于长度。
参考资料
- 本节的练习位于
exercises/04_traits/07_sized