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 的:u32Stringbool 等。

正如我们刚看到的,str 不是 Sized 的。
然而,&strSized!我们在编译时知道它的大小:两个 usize,一个用于指针,一个用于长度。

参考资料

  • 本节的练习位于 exercises/04_traits/07_sized