@ -2,7 +2,7 @@
> [ch04-01-what-is-ownership.md ](https://github.com/rust-lang/book/blob/main/src/ch04-01-what-is-ownership.md )
> < br >
> commit 9c9a522555c05cae6717adfbb419af58ebd1cea0
> commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed
Rust 的核心功能(之一)是 ** 所有权**( *ownership*)。虽然该功能很容易解释,但它对语言的其他部分有着深刻的影响。
@ -107,6 +107,8 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
这个模式对编写 Rust 代码的方式有着深远的影响。现在它看起来很简单,不过在更复杂的场景下代码的行为可能是不可预测的,比如当有多个变量使用在堆上分配的内存时。现在让我们探索一些这样的场景。
< a id = "ways-variables-and-data-interact-move" > < / a >
#### 变量与数据交互的方式(一):移动
在Rust 中,多个变量可以采取不同的方式与同一数据进行交互。让我们看看示例 4-2 中一个使用整型的例子。
@ -129,7 +131,11 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
看看图 4-1 以了解 `String` 的底层会发生什么。`String` 由三部分组成,如图左侧所示:一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。
< img alt = "String in memory" src = "img/trpl04-01.svg" class = "center" style = "width: 50%;" / >
< img alt = "Two tables: the first table contains the representation of s1 on the
stack, consisting of its length (5), capacity (5), and a pointer to the first
value in the second table. The second table contains the representation of the
string data on the heap, byte by byte." src="img/trpl04-01.svg" class="center"
style="width: 50%;" />
< span class = "caption" > 图 4-1: 将值 `"hello"` 绑定给 `s1` 的 `String` 在内存中的表现形式</ span >
@ -143,13 +149,15 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
这个表现形式看起来 ** 并不像** 图 4-3 中的那样,如果 Rust 也拷贝了堆上的数据,那么内存看起来就是这样的。如果 Rust 这么做了,那么操作 `s2 = s1` 在堆上数据比较大的时候会对运行时性能造成非常大的影响。
< img alt = "s1 and s2 to two places" src = "img/trpl04-03.svg" class = "center" style = "width: 50%;" / >
< img alt = "Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap."
src="img/trpl04-02.svg" class="center" style="width: 50%;" />
< span class = "caption" > 图 4-3: 另一个 `s2 = s1` 时可能的内存表现,如果 Rust 同时也拷贝了堆上的数据的话</ span >
之前我们提到过当变量离开作用域后, Rust 自动调用 `drop` 函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 `s2` 和 `s1` 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 ** 二次释放**( *double free*)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
为了确保内存安全,在 `let s2 = s1 ` 之后, Rust 认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;这段代码不能运行:
为了确保内存安全,在 `let s2 = s1 ; ` 之后, Rust 认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;这段代码不能运行:
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-04-cant-use-after-move/src/main.rs:here}}
@ -161,9 +169,13 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
{{#include ../listings/ch04-understanding-ownership/no-listing-04-cant-use-after-move/output.txt}}
```
如果你在其他语言中听说过术语 ** 浅拷贝**( *shallow copy*)和 ** 深拷贝**( *deep copy*),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 ** 移动**( *move*),而不是浅拷贝。上面的例子可以解读为 `s1` 被 ** 移动** 到了 `s2` 中。那么具体发生了什么,如图 4-4 所示。
如果你在其他语言中听说过术语 ** 浅拷贝**( *shallow copy*)和 ** 深拷贝**( *deep copy*),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 ** 移动**( *move*),而不是叫做 浅拷贝。上面的例子可以解读为 `s1` 被 ** 移动** 到了 `s2` 中。那么具体发生了什么,如图 4-4 所示。
< img alt = "s1 moved to s2" src = "img/trpl04-04.svg" class = "center" style = "width: 50%;" / >
< img alt = "Three tables: tables s1 and s2 representing those strings on the
stack, respectively, and both pointing to the same string data on the heap.
Table s1 is grayed out be-cause s1 is no longer valid; only s2 can be used to
access the heap data." src="img/trpl04-04.svg" class="center" style="width:
50%;" />
< span class = "caption" > 图 4-4: `s1` 无效之后的内存表现< / span >
@ -171,6 +183,8 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
另外, 这里还隐含了一个设计选择: Rust 永远也不会自动创建数据的 “深拷贝”。因此,任何 ** 自动** 的复制可以被认为对运行时性能影响较小。
< a id = "ways-variables-and-data-interact-clone" > < / a >
#### 变量与数据交互的方式(二):克隆
如果我们 ** 确实** 需要深度复制 `String` 中堆上的数据,而不仅仅是栈上的数据,可以使用一个叫做 `clone` 的通用函数。第五章会讨论方法语法,不过因为方法在很多语言中是一个常见功能,所以之前你可能已经见过了。