# 何谓所有权

**What is Ownership**?


所谓 *所有权，ownership*，是一套掌管着 Rust 程序如何管理内存的规则。所有程序在其运行期间，都必须管理他们使用计算机内存的方式。有些语言有着可在程序运行时，定期查找不再使用的内存的垃圾回收；而在其他语言中，程序员则必须明确分配和释放内存。Rust 使用了第三种方法：通过带有编译器会检查的一套规则的所有权系统，内存得以管理。如果违反了任何规则，程序将不会编译。在程序运行过程中，所有权的所有特性，都不会减慢程序的运行速度。

由于对于许多程序员来说，所有权是个新概念，因此需要一些时间来适应。好消息是，咱们对 Rust 和所有权系统规则越有经验，咱们就会发现，自然而然地开发出安全高效的代码就越容易。请坚持下去！

当咱们理解了所有权，就为理解 Rust 独特的功能，打下了坚实的基础。在本章中，咱们将通过一些以字符串，这种常见数据结构为重点的示例，来掌握所有权。


> **内存栈与堆**
>
> **The Stack and the Heap**
>
> 许多编程语言，都不要求咱们经常考虑堆栈和堆。但是，在 Rust 这样的系统编程语言中，某个值是在栈上，还是在堆上，会影响这门语言的行为方式，以及咱们必须做出某些决定的原因。本章稍后所有权的一些部分，就会与栈和堆结合讲解，因此在此要预先简要说明。
>
> 栈和堆都是内存的部分，供代码在运行时使用，但他们的结构方式不同。栈是以其获取到值的顺序存储值，并按照相反的顺序移除值。这就是所谓的 *后进先出，last in, first out*。请设想一摞盘子：当咱们添加更多盘子时，就会把他们放到这堆盘子的顶端；当咱们需要某个盘子时，就从顶端取下一个。从中间或底部添加或移除盘子的效果并不好！添加数据被称为 *推入堆栈，pushing onto the stack*，移除数据被称为 *弹出栈，popping off the stack*。栈上存储的所有数据，必须有已知、固定的大小。编译时大小未知，或大小可能改变的数据，必须存储在堆上。
>
> 堆的组织程度则较低：在咱们把数据放在堆上时，咱们要请求一定数量的空间。内存分配器会在堆中，找到足够大的空位，将其标记为在用，并返回一个 *指针，pointer*，即那个位置的地址。这个过程称为 *在堆上分配，allocating on the heap*，有时也简称为 *分配，allocating*（将值推入栈，则不被视为分配）。由于到堆的指针，属于已知、固定的大小，因此咱们可以将该指针，存储在栈上，而当咱们需要具体数据时，就必须跟随这个指针。请设想在餐厅等待安排座位的情景。当咱们进入某家餐厅时，咱们要说明咱们团体的人数，然后接待员会找到一张适合每个人的空桌，并把咱们领到那里。如果咱们团队中有人来晚了，他们可以询问，咱们的座位在哪里，然后找到咱们。
>
> 压入栈要比在堆上分配空间更快，因为分配器无需寻找存储新数据的位置；该位置总是在栈的顶部。相比之下，在堆上分配空间，则需要更多的工作，因为分配器必须首先找到一个足够大的空间来存放数据，然后进行簿记，为下一次分配做好准备。
>
> 访问堆中的数据，比访问栈上的数据要慢，因为咱们必须跟随指针才能到达那里。如果减少在内存中的跳转，那么现代处理器的速度就会更快。继续类比，请设想一下某个餐厅的服务员，从许多桌子上点菜的情况。最有效的方法是先处理一张桌子上的所有点餐，然后再处理下一张桌子上的点餐。从 A 桌点菜，然后从 B 桌点菜，然后再从 A 桌点菜，然后再从 B 桌点菜，这个过程就会慢得多。同样，如果处理器处理的数据，与其他数据距离较近（如栈上的数据），而不是较远（如堆中的数据），那么处理器就能更好地完成工作。
>
> 当咱们的代码调用某个函数时，传入函数的值（可能包括指向堆上数据的指针）和函数的局部变量，会被推入栈。函数结束后，这些值会从栈上弹出。
>
> 跟踪代码的哪些部分，正在使用堆上的哪些数据、尽量减少堆上的重复数据量，以及清理堆上未使用数据以免空间耗尽，这些都是所有权要解决的问题。一旦咱们掌握了所有权，咱们就不需要经常考虑栈和堆了，而清楚所有权的主要目的，是为管理堆数据这一点，有助于解释为什么他以这种方式工作。


## 所有权规则

**Ownership Rules**


首先，我们来看看这些所有权规则。在我们举例说明他们时，请牢记这些规则：


- Rust 中的每个值，都有个 *所有者，owner*；

- 同一时间，只能有一个所有者；

- 当所有者超出作用域时，该值将被丢弃。


## 变量作用域

**Variable Scope**


既然我们已经掌握了 Rust 的基本语法，我们就不会在示例中，包含所有 `fn main() {` 代码，所以如果咱们正在学习，请务必手动将下面的示例，放在某个 `main` 函数中。如此，我们的示例将更加简洁，让我们专注于具体细节，而不是样板代码了。

作为所有权的首个例子，我们来看看，一些变量的作用域。作用域是指某个项目在程序中，有效的范围。以下面的变量为例：


```rust
let s = "hello";
```

变量 `s` 指向某个字符串字面值，其中该字符串的值，被硬编码到我们程序的文本中。这个变量从其被声明时开始，直到当前作用域结束，都是有效的。下面清单 4-1 给出了一个带有说明这个变量 `s` 的于何处有效注释的程序。


```rust
{                       // 变量 s 在这里是无效的，他还没被声明出来
    let s = "hello";    // s 自此往下都是有效的

    // 对变量 s 执行一些操作
}                       // 此时该作用域结束，而变量 s 不再有效
```

*清单 4-1：一个变量及其有效的作用域*


换句话说，这里有两个重要的时间点：


- 当 `s` 进 *入* 作用域时，他便有效了；

- 在超 *出* 作用域之前，他会一直有效。


此时，作用域和变量何时有效之间的关系，与其他编程语言中的类似。现在我们将通过引入 `String` 类型，在这种理解的基础上构建。


## `String` 类型

**The `String` Type**


为了说明所有权规则，我们需要一种比第 3 章 [数据类型](../programming_concepts/data_types.md) 小节中，介绍的数据类型更复杂的一种数据类型。前面介绍的类型大小已知，可被存储在栈上，并在其作用域结束时，从栈上弹出，如果代码的另一部分，需要在不同的作用域中使用同一个值，他们就可以快速、简便地复制，以创建一个新的、独立的实例。但我们打算看看存储在堆上的数据，并探讨 Rust 如何知道，何时清理这些数据，而 `String` 类型就是个很好的例子。

我们将重点关注 `String` 中，与所有权相关的部分。这些方面也适用于其他复杂的数据类型，无论它们是由标准库提供，还是由咱们自己创建。我们将在 [第 8 章](../common_collections/strings.md) 中，更深入地讨论 `String`。

我们已经见过字符串的字面值，其中某个字符串值，被硬编码到咱们的程序中。字符串字面值很方便，但他们并不适合我们打算使用文本的所有情况。其中一个原因是，他们是不可变的。另一个原因是，在我们编写代码时，并非每个字符串值，都是已知的：例如，如果我们打算获取用户输入，并将其存储起来，该怎么办？针对这些情况，Rust 有着第二种字符串类型 -- `String`。该类型管理堆上分配的数据，因此可以存储编译时咱们未知数量的文本。咱们可以使用 `from` 函数,从某个字符串字面值创建出一个 `String`，如下所示：


```rust
let s = String::from("hello");
// 变量 s 的类型为：String, 而此前字面值中的变量 s 的类型为：&str
```


其中双冒号 `::` 操作符，允许我们在 `String` 类型下，命名这个特殊的 `from` 函数，而不是使用诸如 `string_from` 的某种名字。我们将在第 5 章的 [方法语法](../structs/method_syntax.md) 小节，以及第 7 章的 [用于引用模块树中某个项目的路径](../packages_crates_and_modules/paths.md) 小节，讨论模块命名空间时，进一步讨论这种语法。

这种字符串，*可* 被改变：

```rust
    let mut s = String::from("hello");

    s.push_str(", world!"); // push_str() 方法会追加一个字面值，到某个 String

    println! ("{}", s); // 这将打印出 `hello, world!`
```


那么，这里有什么区别呢？为什么 `String` 可被改变，而字面值却不能呢？区别在于，这两种类型如何处理内存。


## 内存与内存分配

**Memeory and Allocation**


对于字符串字面值，我们在编译时就知道其内容，因此该文本会被直接硬编码到，最终的可执行文件中。这就是字符串字面值快速高效的原因。但这些属性，只是来自于字符串字面值的不变性。遗憾的是，我们无法为每段编译时大小未知，且在运行程序时大小可能改变的文本，添加一块内存到二进制程序中。

而在 `String` 类型下，为了支持某段可变、可增长的文本，我们需要在堆上，分配编译时未知的某个数量的内存，来保存内容。这意味着：


- 必须在运行时，向内存分配器申请该内存；

- 我们需要某种，当咱们是使用完咱们的 `String` 后，将此内存返回给分配器的方法。


第一部分是由我们完成的：当我们调用 `String::from` 时，其实现会请求所需的内存。这在编程语言中非常普遍。

不过，第二部分有所不同。在有 *垃圾回收器，garbage collector，GC* 的语言中，GC 会跟踪并清理不再使用的内存，我们不需要考虑这个问题。在大多数没有 GC 的语言中，我们有责任识别内存何时不再被使用，并调用代码显式释放内存，就像我们请求内存时一样。正确做到这一点，历来是编程中的难题。如果我们忘记了，就会浪费内存。如果过早释放，就会产生无效变量。如果我们做了两次，那也是一个错误。我们需要在 `allocate` 一次内存的同时，严格 `free` 一次内存。

Rust 采取了不同路径：一旦拥有内存的变量超出作用域，该内存就会自动退回。下面是清单 4-1 中，咱们作用域示例的一个使用了 `String`，而非字符串字面值的版本：


```rust
    {
        let s = String::from("hello");  // 变量 s 自此往下是有效的

        // 以变量 s 完成一些操作
    }                                   // 该作用域到此时结束，而变量 s
                                        // 不再有效
```


其中有个我们可将 `String` 所需的内存，归还给分配器的天然时间点：当 `s` 超出作用域时。当某个变量超出作用域时，Rust 会为我们，调用一个特殊函数。这个函数叫做 `drop`，这便是 `String` 的作者，可将返回内存代码放置的地方。Rust 会在那个结尾的大括号处，自动调用 `drop` 函数。


> **注意**：在 C++ 中，这种在某个项目生命周期结束时，解分配资源的模式，有时被称为 *资源获取即初始化，Resource Acquisition Is Initialization，RAII*。如果咱们使用过 RAII 模式，那么 Rust 中的 `drop` 函数对咱们一定不会陌生。


这种模式对 Rust 代码的编写方式，影响深远。现在看来可能很简单，但在当我们打算让多个变量，使用我们在堆上分配的数据，这种更为复杂的情况下，代码的行为就会出乎意料。现在我们来探讨一下，其中的一些情况。


### 变量与数据相互作用：迁移

**Variables and Data Interacting with Move**


在 Rust 中，多个变量可以不同的方式，与同一数据交互。我们来看看清单 4-2 中，使用整数的示例。


```rust
let x = 5;
let y = x;
```

*清单 4-2：将变量 `x` 的整数值，赋值给 `y`*


我们大致可以猜到这是在做什么： “将值 `5` 绑定到 `x`；然后构造 `x` 中值的一份拷贝，并将其绑定到 `y`”。现在我们有两个变量，`x` 和 `y`，他们都等于 `5`。这确实是正在发生的事情，因为整数属于有着已知、固定大小的简单值，而这两个 `5` 值，都会被压入栈上。


> **注**：这就是下面会讲到的 [唯栈数据：拷贝](#唯栈数据拷贝stack-only-data-copy) 情形。


现在我们来看看 `String` 这个版本：


```rust
let s1 = String::from("hello");
let s2 = s1;
```

这看起来非常相似，因此我们可能会认为，其工作方式是一样的：即第二行构造出一份 `s1` 中值的拷贝，并将其绑定到 `s2`。但事实并非如此。


> **注**：下面的代码将打印出 `s1: 你好!, s2: 你好!`，表示类型 `&str` （字符串切片）是存储在栈上的。

```rust
fn main() {
    let s1 = "你好！";
    let s2 = s1;

    println! ("s1: {s1}, s2: {s2}");
}
```


请看图 4-1，了解 `String` 的到底发生了什么。一个 `String` 由三部分组成（如左图所示）：指向存放该字符串内容的内存的一个指针、一个长度与一个容量。这组数据，是存储在栈上的。而图的右边，则是堆上存放着内容的内存。


![Rust 中 `String` 类型的本质](../images/Ch04_01.svg)

*图 4-1：保存绑定到 `s1` 的值 `"hello"` 的 `String` 在内存中的表示*

> **注**：`String` 类似属于 [灵巧指针](../Ch15_Smart_Pointers.md)，他是个包含了指针与其他一些元数据，有着一些方法特别能力的结构体。


长度是这个 `String` 的内容当前所使用的内存容量（以字节为单位）。容量则是这个字符串，从内存分配器获得的内存总量（以字节为单位）。长度和容量之间的差异很重要，但在现在这个上下文中并不重要，所以忽略这个容量就可以了。

当我们把 `s1` 赋值给 `s2` 时，这个 `String` 数据会被复制，这意味着我们拷贝了栈上的指针、长度和容量。我们不会拷贝该指针所指向的堆上数据。换句话说，内存中的数据表示，会如下图 4-2 所示。


![有着变量 `s1` 的指针、长度与容量拷贝的变量 `s2` 在内存中的表示](../images/Ch04_02.svg)

*图 4-2：变量 `s2` 在内存中的表示，其有着 `s1` 的指针、长度和容量的副本*


如果 Rust 也拷贝了堆数据，那么内存的表示就不会如图 4-3 所示。如果 Rust 这样做了，那么在堆上的数据很大时，操作 `s2 = s1` 在运行时的性能方面，可能会非常昂贵。

![`s2 = s1` 操作的另一种可能：Rust 拷贝内存堆数据](../images/Ch04_03.svg)

*图 4-3：如果 Rust 也复制了堆数据，则 `s2 = s1` 可能会执行的另一种可能性*


前面我们说过，当某个变量超出作用域时，Rust 会自动调用 `drop` 函数，并清理该变量的堆内存。但图 4-2 显示，两个数据指针都指向同一位置。这就有问题了：当 `s2` 和 `s1` 超出作用域时，他们都会尝试释放相同的内存。这就是所谓的 *双重释放，double free* 错误，也是我们之前提到的内存安全漏洞之一。释放两次内存，会导致内存损坏，从而可能导致安全漏洞。

为了确保内存安全，在 `let s2 = s1;` 之后，Rust 便将 `s1` 视为不再有效。因此，当 `s1` 离开作用域时，Rust 不需要释放任何东西。请在` s2` 创建出之后，看看尝试使用 `s1` 时会发生什么；他不会工作：


```rust
    let s1 = String::from("hello");
    let s2 = s1;

    println! ("{}, world!", s1);
```


因为 Rust 阻止咱们使用无效引用，咱们就会得到一个下面这样的报错：


```console
$ cargo run
   Compiling ownership_demo v0.1.0 (/home/peng/rust-lang-zh_CN/projects/ownership_demo)
warning: unused variable: `s2`
 --> src/main.rs:3:9
  |
3 |     let s2 = s1;
  |         ^^ help: if this is intentional, prefix it with an underscore: `_s2`
  |
  = note: `#[warn(unused_variables)]` on by default

error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:29
  |
2 |     let s1 = String::from("hello");
  |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 |     let s2 = s1;
  |              -- value moved here
4 |
5 |     println! ("{}, world!", s1);
  |                             ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let s2 = s1.clone();
  |                ++++++++

For more information about this error, try `rustc --explain E0382`.
warning: `ownership_demo` (bin "ownership_demo") generated 1 warning
error: could not compile `ownership_demo` (bin "ownership_demo") due to previous error; 1 warning emitted
```

如果咱们在使用其他语言时，听说过 *浅拷贝，shallow copy* 和 *深拷贝，deep copy* 这两个术语，那么拷贝指针、长度和容量，而不拷贝数据的这个概念，可能听起来就像是在构造一份浅拷贝。但是，由于 Rust 还会使第一个变量失效，所以这不叫浅拷贝，而叫 *迁移，move*。在这个例子中，我们会说 `s1` 被迁移到了 `s2` 中。因此，实际发生的情况，如下图 4-4 所示。


![在变量 `s1` 失效后内存中的表示](../images/Ch04_04.svg)

*图 4-4：变量 `s1` 被失效后内存中的表示*


这就解决了我们的问题！由于只有 `s2` 有效，当他超出作用域时，只有他将释放内存，我们就大功告成了。

此外，这还暗含一种设计取舍： Rust 绝不会自动创建咱们数据的 “深” 拷贝。因此，可以假定任何 *自动* 拷贝，在运行时性能方面，都是低成本的。


### 变量与数据相互作用：克隆

**Variables and Data Interacting with Clone**


在我们 *确实* 想要深度拷贝该 `String` 的堆数据，而不仅仅是其栈数据时，我们可以使用一个名为 `clone` 的常用方法。我们将在第 5 章讨论方法语法，但由于方法是许多编程语言的共同特征，你可能已经见过了。

下面是这个 `clone` 方法的一个实际应用示例：


```rust
    let s1 = String::from("hello");
    let s2 = s1.clone();

    println! ("s1 = {s1}, s2 = {s2}");
```


这工作得很好，并会显式地产生如图 4-3 所示的行为，其中堆数据 *确实* 得以拷贝。


当咱们看到某个对 `clone` 的调用时，咱们就知道一些任意代码正在被执行，而这些代码可能很昂贵。这是个直观的指示器，表明正在发生一些不同寻常的事情。


### 唯栈数据：拷贝

**Stack-Only Data: Copy**


还有一个咱们尚未谈到过的问题。使用整数的这段代码 -- 其中一部分已在清单 4-2 中给出 -- 是有效的：


```rust
let x = 5;
let y = x;

println! ("x = {}, y = {}", x, y);
```


但这段代码似乎与我们刚刚学到的内容相矛盾：我们没有调用 `clone`，但 `x `仍然有效，而没有被迁移到 `y` 中。

原因是像整数这种在编译时就已知大小的类型，会被整个地存储在栈上，所以具体值的拷贝很快就构造出来了。换句话说，这里的深拷贝和浅拷贝，没有区别，所以调用 `clone` 与通常的浅拷贝，就不会有什么不同，我们可以不使用他。

Rust 有个名为 `Copy` 特质的特殊注解，我们可以把他放在像整数这种，存储在栈上的类型上（我们将在 [第 10 章](../generic_types_traits_and_lifetimes/traits.md) 详细讨论特质）。在某个类型实现了 `Copy` 特质时，用到他的变量就不会迁移，而是被简单拷贝，从而令到他们在赋值给另一个变量后，仍然有效。

如果某个类型或其任何部分，已经实现了 `Drop` 这个特质，Rust 就不会让我们以 `Copy` 注解该类型了。如果该类型需要在其值超出作用域时，发生某些特殊事情，且我们又为该类型添加了 `Copy` 注解，我们将得到一个编译时报错。要了解如何把 `Copy` 注解添加到咱们的类型，以实现这个特质，请参阅附录 C 中的 [派生特质](../appendix/derivable_traits.md)。

那么，哪些类型实现了 `Copy` 特质呢？咱们可以查看给定类型的文档来确定，但一般来说，任何一组简单的标量值，都可以实现 `Copy`，而任何要求内存分配，或属于某种形式资源的类型，都不能实现 `Copy`。下面是一些实现了 `Copy` 的类型：

- 所有整数类型，如 `u32`；

- 布尔类型，`bool`，值为 `true` 及 `false`；

- 所有浮点类型，如 `f64`；

- 字符类型 `char`；

- 只包含同时实现了 `Copy` 类型的元组。例如，`(i32, i32)` 实现了 `Copy`，但 `(i32, String)` 则没有。


## 所有权与函数

**Ownership and Functions**

将某个值传递给函数的机制，与为将一个值赋给某个变量类似。将一个变量传递给某个函数，会像赋值一样，进行迁移或拷贝。下面清单 4-3 有着一个，带有一些显示其中变量进入和超出作用域位置注释的示例。


文件名：`src/main.rs`

```rust
fn main() {
    let s = String::from("hello");  // s 进到作用域

    takes_ownership(s);             // 变量 s 的值迁移到这个函数中......
                                    // ......进而在这里不再有效

    let x = 5;                      // x 进到作用域

    makes_copy(x);                  // x 将迁移到这个函数中，
                                    // 但由于 i32 实现了 `Copy` 特质，因此
                                    // 后面在使用变量 x 没有问题
    println! ("x = {x}");
}   // 到这里，x 超出作用域，接着是 s。但由于 s 的值已被迁移，因此
    // 不会有特别的事情发生。

fn takes_ownership(some_string: String) {   // some_string 进到作用域
    println! ("{}", some_string);
}   // 这里，some_string 超出作用域，而 `drop` 方法会被调用。退回的
    // 内存被释放。

fn makes_copy(some_integer: i32) {  // some_integer 进到作用域
    println! ("{}", some_integer);
}   // 这里，some_integer 超出作用域。没有特别事情发生。
```

*清单 4-3：带有所有权与作用域注解的函数*


> **注**：下面的代码，仍然会报出：`use of moved value: ``some_string```错误：


```rust
fn takes_ownership(some_string: String) {
    println! ("{}", some_string);
    another_takes_ownership(some_string);
    third_takes_ownership(some_string);
}
```

如果我们试图在调用 `takes_ownership` 后使用 `s`，Rust 就会抛出一个编译时报错。这些静态检查，可以防止我们犯错。请尝试在 `main` 中，添加使用 `s` 和 `x` 的代码，看看在哪些地方咱们可以使用他们，在哪些地方所有权规则会阻止咱们这样做。


## 返回值与作用域

**Return Values and Scope**


返回值也会转移所有权。下面清单 4-4 给出了一个返回某个值的函数示例，有着与清单 4-3 中类似的注释。


文件名：`src/main.rs`

```rust
fn main() {
    let s1 = gives_ownership();         // gives_ownership 会将其返回值
                                        // 迁移到 s1 中

    let s2 = String::from("hello");     // s2 进入作用域

    let s3 = takes_and_gives_bake(s2);  // s2 会被迁移到 takes_and_gives_back
                                        // 中，其又会将他的返回值迁移到 s3 中

    println! ("s1: {s1}, s3: {s3}");
}   // 这里，s3 会超出作用域而被丢弃。变量 s2 已被迁移，因此什么也不会发生。而
    // s1 则超出作用域而被丢弃。

fn gives_ownership() -> String {                // gives_ownership 将把他的返回值，迁移
                                                // 到调用他的函数中（即 main 函数）

    let some_string = String::from("归你了");   // some_string 进到作用域

    some_string                                 // some_string 被返回并
                                                // 迁出到调用函数
}

// 此函数取个 String 并会返回一个 String
fn takes_and_gives_bake(a_string: String) -> String {   // a_string 进入作用域
    a_string    // a_string 被返回，并会迁出到调用函数
}
```

*清单 4-4：转移返回值的所有权*


变量的所有权每次都会遵循同一模式：将某个值赋值给另一变量，就会迁移他。当某个包含了堆中数据的变量，超出作用域时，除非该数据的所有权已被迁移到另一变量，否则该值就将被 `drop` 清理。

虽然这种模式可行，但在每个函数中取得所有权，并返回所有权，会有点繁琐。如果我们打算让某个函数使用一个值，但又不打算取得所有权，该怎么办呢？如果我们传入的任何数据，在我们打算再次使用时，都需要传回，此外，我们可能还打算返回函数主体产生的任何数据，这一点非常恼人。

Rust 确实允许咱们使用元组返回多个值，如下清单 4-5 中所示。


文件名：`src/main.rs`

```rust
fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println! ("字符串 '{}' 的长度为：{}", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();

    (s, length)
}
```

*清单 4-5：返回参数的所有权*

但是，对于一个本应很常见的概念来说，这样做太过仪式化和工作量都太大了。幸运的是，Rust 有一种使用某个值，而不转移所有权的特性，叫做 *引用，references*。
