字符串切片

在之前的章节中,你已经见过不少字符串字面量被用于代码中,比如 "待办事项""票据描述"。它们后面常常跟着 .to_string().into() 的调用。现在是时候理解这么做的原因了!

字符串字面量

通过将原始文本包含在双引号中,你可以定义一个字符串字面量:

#![allow(unused)]
fn main() {
let s = "你好,世界!";
}

s 的类型是 &str,即指向字符串切片的引用

内存布局

&strString 是不同的类型——它们不能互换。让我们回顾一下之前探索过的 String内存布局。如果我们运行:

#![allow(unused)]
fn main() {
let mut s = String::with_capacity(5);
s.push_str("Hello");
}

在内存中会得到这样的情况:

      +---------+--------+----------+
Stack | pointer | length | capacity | 
      |  |      |   5    |    5     |
      +--|------+--------+----------+
         |
         |
         v
       +---+---+---+---+---+
Heap:  | H | e | l | l | o |
       +---+---+---+---+---+

如果你还记得,我们也检查过 &String 在内存中的布局:

      --------------------------------------
      |                                    |         
 +----v----+--------+----------+      +----|----+
 | pointer | length | capacity |      | pointer |
 |    |    |   5    |    5     |      |         |
 +----|----+--------+----------+      +---------+
      |        s                          &s 
      |       
      v       
    +---+---+---+---+---+
    | H | e | l | l | o |
    +---+---+---+---+---+

&String 指向存储 String 元数据的内存位置。如果我们跟随指针,就能到达堆分配的数据,特别是字符串的第一个字节

如果我们想要一个类型来表示 s子字符串呢?比如在 "你好" 中的 "好世界"

字符串切片

&str 是对字符串的一个视图,是对存储在别处的 UTF-8 字节序列的引用。例如,你可以像这样从 String 创建一个 &str

#![allow(unused)]
fn main() {
let mut s = String::with_capacity(5);
s.push_str("你好");
// 从 `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 |                  |
         +---+---+---+---+---+                  |
               ^                                |
               |                                |
               +--------------------------------+

slice 在堆栈上存储了两部分信息:

  • 指向切片第一个字节的指针。
  • 切片的长度。

slice 并不拥有数据,它只是指向数据:它是对 String 堆分配数据的引用。当 slice 被丢弃时,堆分配的数据不会被释放,因为它仍由 s 所拥有。这就是为什么 slice 没有 容量 字段的原因:它不拥有数据,所以不需要知道为数据分配了多少空间;它只关心它所引用的数据。

&str&String 的区别

一般来说,当你需要对文本数据的引用时,优先使用 &str 而不是 &String&str 更加灵活,并且通常被认为是 Rust 代码中更符合习惯的用法。

如果一个方法返回 &String,你是在承诺某处存在与你返回引用所匹配的堆分配的 UTF-8 文本。相反,如果一个方法返回 &str,则拥有更多的灵活性:你只是说某处有一段文本数据,并且其中的一部分与你需要的匹配,因此你返回对这部分的引用。

参考练习

  • 本节的练习位于 exercises/04_traits/05_str_slice