update to ch04-02

pull/584/head
KaiserY 3 years ago
parent 23278a0c39
commit b1a0ca6e85

@ -1,9 +1,9 @@
fn main() {
// ANCHOR: here
{ // s is not valid here, its not yet declared
let s = "hello"; // s is valid from this point forward
{ // s 在这里无效, 它尚未声明
let s = "hello"; // 从此处起s 是有效的
// do stuff with s
} // this scope is now over, and s is no longer valid
// 使用 s
} // 此作用域已结束s 不再有效
// ANCHOR_END: here
}

@ -1,23 +1,23 @@
fn main() {
let s = String::from("hello"); // s comes into scope
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x comes into scope
let x = 5; // x 进入作用域
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still
// use x afterward
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,
// 所以在后面可继续使用 x
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
} // 这里, x 先移出了作用域,然后是 s。但因为 s 的值已被移走,
// 没有特殊之处
fn takes_ownership(some_string: String) { // some_string comes into scope
fn takes_ownership(some_string: String) { // some_string 进入作用域
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
} // 这里some_string 移出作用域并调用 `drop` 方法。
// 占用的内存被释放
fn makes_copy(some_integer: i32) { // some_integer comes into scope
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
} // 这里some_integer 移出作用域。没有特殊之处

@ -1,29 +1,29 @@
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1
let s1 = gives_ownership(); // gives_ownership 将返回值
// 转移给 s1
let s2 = String::from("hello"); // s2 comes into scope
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 离开作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it
fn gives_ownership() -> String { // gives_ownership 会将
// 返回值移动给
// 调用它的函数
let some_string = String::from("yours"); // some_string comes into scope
let some_string = String::from("yours"); // some_string 进入作用域.
some_string // some_string is returned and
// moves out to the calling
// function
some_string // 返回 some_string
// 并移出给调用的函数
//
}
// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
//
a_string // a_string is returned and moves out to the calling function
a_string // 返回 a_string 并移出给调用的函数
}

@ -7,7 +7,7 @@ fn main() {
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() returns the length of a String
let length = s.len(); // len() 返回字符串的长度
(s, length)
}

@ -2,8 +2,8 @@ fn main() {
// ANCHOR: here
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() appends a literal to a String
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // This will print `hello, world!`
println!("{}", s); // 将打印 `hello, world!`
// ANCHOR_END: here
}

@ -1,10 +1,10 @@
fn main() {
// ANCHOR: here
{
let s = String::from("hello"); // s is valid from this point forward
let s = String::from("hello"); // 从此处起s 是有效的
// do stuff with s
} // this scope is now over, and s is no
// longer valid
// 使用 s
} // 此作用域已结束,
// s 不再有效
// ANCHOR_END: here
}

@ -9,6 +9,6 @@ fn main() {
// ANCHOR: here
fn calculate_length(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
// it refers to, nothing happens.
} // 这里s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生
// ANCHOR_END: here

@ -4,7 +4,7 @@ fn main() {
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
// ANCHOR_END: here

@ -2,9 +2,9 @@ fn main() {
// ANCHOR: here
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);
// ANCHOR_END: here

@ -2,12 +2,12 @@ fn main() {
// ANCHOR: here
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // no problem
let r3 = &mut s; // 没问题
println!("{}", r3);
// ANCHOR_END: here
}

@ -3,11 +3,11 @@ fn main() {
}
// ANCHOR: here
fn dangle() -> &String { // dangle returns a reference to a String
fn dangle() -> &String { // dangle 返回一个字符串的引用
let s = String::from("hello"); // s is a new String
let s = String::from("hello"); // s 是一个新字符串
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!
// ANCHOR_END: here

@ -2,6 +2,6 @@
> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/main/src/ch04-00-understanding-ownership.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit a5e0c5b2c5f9054be3b961aea2c7edfeea591de8
所有权(系统)是 Rust 最为与众不同的特性,它让 Rust 无需垃圾回收garbage collector即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。本章我们将讲到所有权以及相关功能借用、slice 以及 Rust 如何在内存中布局数据。
所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收garbage collector即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。本章,我们将讲到所有权以及相关功能:借用borrowing、slice 以及 Rust 如何在内存中布局数据。

@ -2,11 +2,11 @@
> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/main/src/ch04-01-what-is-ownership.md)
> <br>
> commit d377d2effa9ae9173026e35a7e464b8f5b82409a
> commit a5e0c5b2c5f9054be3b961aea2c7edfeea591de8
Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该功能很容易解释,但它对语言的其他部分有着深刻的影响。
所有运行的程序都必须管理其使用计算机内存的方式。一些语言中具有垃圾回收机制在程序运行时不断地寻找不再使用的内存在另一些语言中程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。在运行时,所有权系统的任何功能都不会减慢程序。
所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制在程序运行时不断地寻找不再使用的内存在另一些语言中程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。
因为所有权对很多程序员来说都是一个新概念,需要一些时间来适应。好消息是随着你对 Rust 和所有权系统的规则越来越有经验,你就越能自然地编写出安全和高效的代码。持之以恒!
@ -16,11 +16,8 @@ Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该
>
> 在很多语言中,你并不需要经常考虑到栈与堆。不过在像 Rust 这样的系统编程语言中,值是位于栈上还是堆上在更大程度上影响了语言的行为以及为何必须做出这样的抉择。我们会在本章的稍后部分描述所有权与栈和堆相关的内容,所以这里只是一个用来预热的简要解释。
>
> 栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 **后进先出***last in, first out*)。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 **进栈***pushing onto the stack*),而移出数据叫做 **出栈***popping off the stack*)。
>
> 栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据要改为存储在堆上。堆是缺乏组织的当向堆放入数据时你要请求一定大小的空间。内存分配器memory allocator在堆的某处找到一块足够大的空位把它标记为已使用并返回一个表示该位置地址的 **指针***pointer*)。这个过程称作 **在堆上分配内存***allocating on the heap*),有时简称为 “分配”allocating。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的你可以将指针存储在栈上不过当需要实际数据时必须访问指针。
>
> 想象一下去餐馆就座吃饭。当进入时,你说明有几个人,餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了,他们也可以通过询问来找到你们坐在哪。
> 栈和堆都是代码在运行时可供使用的内存,但是它们的结构不同。栈以放入值的顺序存储值并以相反顺序取出值。这也被称作 **后进先出***last in, first out*)。想象一下一叠盘子:当增加更多盘子时,把它们放在盘子堆的顶部,当需要盘子时,也从顶部拿走。不能从中间也不能从底部增加或拿走盘子!增加数据叫做 **进栈***pushing onto the stack*),而移出数据叫做 **出栈***popping off the stack*)。栈中的所有数据都必须占用已知且固定的大小。在编译时大小未知或大小可能变化的数据,要改为存储在堆上。
> 堆是缺乏组织的当向堆放入数据时你要请求一定大小的空间。内存分配器memory allocator在堆的某处找到一块足够大的空位把它标记为已使用并返回一个表示该位置地址的 **指针***pointer*)。这个过程称作 **在堆上分配内存***allocating on the heap*),有时简称为 “分配”allocating。将数据推入栈中并不被认为是分配。因为指针的大小是已知并且固定的你可以将指针存储在栈上不过当需要实际数据时必须访问指针。想象一下去餐馆就座吃饭。当进入时你说明有几个人餐馆员工会找到一个够大的空桌子并领你们过去。如果有人来迟了他们也可以通过询问来找到你们坐在哪。
>
> 入栈比在堆上分配内存要快,因为(入栈时)分配器无需为存储新数据去搜索内存空间;其位置总是在栈顶。相比之下,在堆上分配内存则需要更多的工作,这是因为分配器必须首先找到一块足够存放数据的内存空间,并接着做一些记录为下一次分配做准备。
>
@ -28,7 +25,7 @@ Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该
>
> 当你的代码调用一个函数时,传递给函数的值(包括可能指向堆上数据的指针)和函数的局部变量被压入栈中。当函数结束时,这些值被移出栈。
>
> 跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的存在就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。
> 跟踪哪部分代码正在使用堆上的哪些数据,最大限度的减少堆上的重复数据的数量,以及清理堆上不再使用的数据确保不会耗尽空间,这些问题正是所有权系统要处理的。一旦理解了所有权,你就不需要经常考虑栈和堆了,不过明白了所有权的主要目的就是为了管理堆数据,能够帮助解释为什么所有权要以这种方式工作。
### 所有权规则
@ -40,7 +37,7 @@ Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该
### 变量作用域
我们已经在第二章完成一个 Rust 程序示例。既然我们已经掌握了基本语法,将不会在之后的例子中包含 `fn main() {` 代码,所以如果你是一路跟过来的,必须手动将之后例子的代码放入一个 `main` 函数中。这样,例子将显得更加简明,使我们可以关注实际细节而不是样板代码。
既然我们已经掌握了基本语法,将不会在之后的例子中包含 `fn main() {` 代码,所以如果你是一路跟过来的,必须手动将之后例子的代码放入一个 `main` 函数中。这样,例子将显得更加简明,使我们可以关注实际细节而不是样板代码。
在所有权的第一个例子中,我们看看一些变量的 **作用域***scope*。作用域是一个项item在程序中有效的范围。假设有这样一个变量
@ -48,14 +45,10 @@ Rust 的核心功能(之一)是 **所有权***ownership*)。虽然该
let s = "hello";
```
变量 `s` 绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前 **作用域** 结束时都是有效的。示例 4-1 的注释标明了变量 `s` 在何处是有效的。
变量 `s` 绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前 **作用域** 结束时都是有效的。示例 4-1 的注释标明了变量 `s` 在何处是有效的。
```rust
{ // s 在这里无效, 它尚未声明
let s = "hello"; // 从此处起s 是有效的
// 使用 s
} // 此作用域已结束s 不再有效
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-01/src/main.rs:here}}
```
<span class="caption">示例 4-1一个变量和其有效的作用域</span>
@ -71,7 +64,7 @@ let s = "hello";
为了演示所有权的规则,我们需要一个比第三章 [“数据类型”][data-types] 中讲到的都要复杂的数据类型。前面介绍的类型都是已知大小的,可以存储在栈中,并且当离开作用域时被移出栈,如果代码的另一部分需要在不同的作用域中使用相同的值,可以快速简单地复制它们来创建一个新的独立实例。不过我们需要寻找一个存储在堆上的数据来探索 Rust 是如何知道该在何时清理数据的。
这里使用 `String` 作为例子,并专注于 `String` 与所有权相关的部分。这些方面也同样适用于标准库提供的或你自己创建的其他复杂数据类型。在第八章会更深入地讲解 `String`
我们会专注于 `String` 与所有权相关的部分。这些方面也同样适用于标准库提供的或你自己创建的其他复杂数据类型。在[第八章][ch8]会更深入地讲解 `String`
我们已经见过字符串字面值即被硬编码进程序里的字符串值。字符串字面值是很方便的不过它们并不适合使用文本的每一种场景。原因之一就是它们是不可变的。另一个原因是并非所有字符串的值都能在编写代码时就知道例如要是想获取用户输入并存储该怎么办呢为此Rust 有第二个字符串类型,`String`。这个类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。可以使用 `from` 函数基于字符串字面值来创建 `String`,如下:
@ -79,16 +72,12 @@ let s = "hello";
let s = String::from("hello");
```
这两个冒号`::`是运算符,允许将特定的 `from` 函数置于 `String` 类型的命名空间namespace而不需要使用类似 `string_from` 这样的名字。在第五章的 [“方法语法”“Method Syntax”][method-syntax] 部分会着重讲解这个语法而且在第七章的 [“路径用于引用模块树中的项”][paths-module-tree] 中会讲到模块的命名空间。
这两个冒号 `::` 是运算符,允许将特定的 `from` 函数置于 `String` 类型的命名空间namespace而不需要使用类似 `string_from` 这样的名字。在第五章的 [“方法语法”“Method Syntax”][method-syntax] 部分会着重讲解这个语法而且在第七章的 [“路径用于引用模块树中的项”][paths-module-tree] 中会讲到模块的命名空间。
**可以** 修改此类字符串
```rust
let mut s = String::from("hello");
s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-01-can-mutate-string/src/main.rs:here}}
```
那么这里有什么区别呢?为什么 `String` 可变而字面值却不行呢?区别在于两个类型对内存的处理上。
@ -104,17 +93,12 @@ println!("{}", s); // 将打印 `hello, world!`
第一部分由我们完成:当调用 `String::from` 时,它的实现 (*implementation*) 请求其所需的内存。这在编程语言中是非常通用的。
然而,第二部分实现起来就各有区别了。在有 **垃圾回收***garbage collector**GC*)的语言中, GC 记录并清除不再使用的内存,而我们并不需要关心它。没有 GC 的话,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 `allocate` 配对一个 `free`
然而,第二部分实现起来就各有区别了。在有 **垃圾回收***garbage collector**GC*)的语言中, GC 记录并清除不再使用的内存,而我们并不需要关心它。在大部分没有 GC 的语言中,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。从历史的角度上说正确处理内存回收曾经是一个困难的编程问题。如果忘记回收了会浪费内存。如果过早回收了,将会出现无效变量。如果重复回收,这也是个 bug。我们需要精确的为一个 `allocate` 配对一个 `free`
Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域后就被自动释放。下面是示例 4-1 中作用域例子的一个使用 `String` 而不是字符串字面值的版本:
```rust
{
let s = String::from("hello"); // 从此处起s 是有效的
// 使用 s
} // 此作用域已结束,
// s 不再有效
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-02-string-scope/src/main.rs:here}}
```
这是一个将 `String` 需要的内存返回给分配器的很自然的位置:当 `s` 离开作用域的时候。当变量离开作用域Rust 为我们调用一个特殊的函数。这个函数叫做 [`drop`][drop],在这里 `String` 的作者可以放置释放内存的代码。Rust 在结尾的 `}` 处自动调用 `drop`
@ -128,8 +112,7 @@ Rust 采取了一个不同的策略:内存在拥有它的变量离开作用域
在Rust 中,多个变量可以采取不同的方式与同一数据进行交互。让我们看看示例 4-2 中一个使用整型的例子。
```rust
let x = 5;
let y = x;
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-02/src/main.rs:here}}
```
<span class="caption">示例 4-2将变量 `x` 的整数值赋给 `y`</span>
@ -139,8 +122,7 @@ let y = x;
现在看看这个 `String` 版本:
```rust
let s1 = String::from("hello");
let s2 = s1;
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-03-string-move/src/main.rs:here}}
```
这看起来与上面的代码非常类似,所以我们可能会假设他们的运行方式也是类似的:也就是说,第二行可能会生成一个 `s1` 的拷贝并绑定到 `s2` 上。不过,事实上并不完全是这样。
@ -167,33 +149,16 @@ let s2 = s1;
之前我们提到过当变量离开作用域后Rust 自动调用 `drop` 函数并清理变量的堆内存。不过图 4-2 展示了两个数据指针指向了同一位置。这就有了一个问题:当 `s2``s1` 离开作用域,他们都会尝试释放相同的内存。这是一个叫做 **二次释放***double free*)的错误,也是之前提到过的内存安全性 bug 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
为了确保内存安全,这种场景下 Rust 的处理有另一个细节值得注意。`let s2 = s1` 之后Rust 认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;这段代码不能运行:
为了确保内存安全,在 `let s2 = s1` 之后Rust 认为 `s1` 不再有效,因此 Rust 不需要在 `s1` 离开作用域后清理任何东西。看看在 `s2` 被创建之后尝试使用 `s1` 会发生什么;这段代码不能运行:
```rust,ignore,does_not_compile
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-04-cant-use-after-move/src/main.rs:here}}
```
你会得到一个类似如下的错误,因为 Rust 禁止你使用无效的引用。
```console
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:28
|
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
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ownership` due to previous error
{{#include ../listings/ch04-understanding-ownership/no-listing-04-cant-use-after-move/output.txt}}
```
如果你在其他语言中听说过术语 **浅拷贝***shallow copy*)和 **深拷贝***deep copy*),那么拷贝指针、长度和容量而不拷贝数据可能听起来像浅拷贝。不过因为 Rust 同时使第一个变量无效了,这个操作被称为 **移动***move*),而不是浅拷贝。上面的例子可以解读为 `s1`**移动** 到了 `s2` 中。那么具体发生了什么,如图 4-4 所示。
@ -213,10 +178,7 @@ error: could not compile `ownership` due to previous error
这是一个实际使用 `clone` 方法的例子:
```rust
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-05-clone/src/main.rs:here}}
```
这段代码能正常运行,并且明确产生图 4-3 中行为,这里堆上的数据 **确实** 被复制了。
@ -228,10 +190,7 @@ println!("s1 = {}, s2 = {}", s1, s2);
这里还有一个没有提到的小窍门。这些代码使用了整型并且是有效的,他们是示例 4-2 中的一部分:
```rust
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-06-copy/src/main.rs:here}}
```
但这段代码似乎与我们刚刚学到的内容相矛盾:没有调用 `clone`,不过 `x` 依然有效且没有被移动到 `y` 中。
@ -255,27 +214,7 @@ Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let s = String::from("hello"); // s 进入作用域
takes_ownership(s); // s 的值移动到函数里 ...
// ... 所以到这里不再有效
let x = 5; // x 进入作用域
makes_copy(x); // x 应该移动函数里,
// 但 i32 是 Copy 的,所以在后面可继续使用 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 移出作用域。不会有特殊操作
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-03/src/main.rs}}
```
<span class="caption">示例 4-3带有所有权和作用域注释的函数</span>
@ -284,69 +223,34 @@ fn makes_copy(some_integer: i32) { // some_integer 进入作用域
### 返回值与作用域
返回值也可以转移所有权。示例 4-4 与示例 4-3 一样带有类似的注释。
返回值也可以转移所有权。示例 4-4 展示了一个返回了某些值的示例,与示例 4-3 一样带有类似的注释。
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 移出作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 将返回值移动给
// 调用它的函数
let some_string = String::from("hello"); // some_string 进入作用域.
some_string // 返回 some_string 并移出给调用的函数
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-04/src/main.rs}}
```
<span class="caption">示例 4-4: 转移返回值的所有权</span>
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。
虽然这样是可以的,但是在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。
我们可以使用元组来返回多个值,如示例 4-5 所示。
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len() 返回字符串的长度
(s, length)
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-05/src/main.rs}}
```
<span class="caption">示例 4-5: 返回参数的所有权</span>
但是这未免有些形式主义而且这种场景应该很常见。幸运的是Rust 对此提供了一个功能,叫做 **引用***references*)。
但是这未免有些形式主义而且这种场景应该很常见。幸运的是Rust 对此提供了一个不用获取所有权就可以使用值的功能,叫做 **引用***references*)。
[data-types]: ch03-02-data-types.html#数据类型
[ch8]: ch08-02-strings.html
[derivable-traits]: appendix-03-derivable-traits.html
[method-syntax]: ch05-03-method-syntax.html#方法语法
[paths-module-tree]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html

@ -2,31 +2,17 @@
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md)
> <br>
> commit d377d2effa9ae9173026e35a7e464b8f5b82409a
> commit 8cf0496bb8e56b683ea3f015871c8631684decf4
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。
下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权:
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。相反我们可以提供一个 `String` 值的引用reference。**引用***reference*)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用确保指向某个特定类型的有效值。下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权:
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:all}}
```
首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 `&s1``calculate_length`,同时在函数定义中,我们获取 `&String` 而不是 `String`
这些 & 符号就是 **引用**,它们允许你使用值但不获取其所有权。图 4-5 展示了一张示意图。
首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 `&s1``calculate_length`,同时在函数定义中,我们获取 `&String` 而不是 `String`。这些 & 符号就是 **引用**,它们允许你使用值但不获取其所有权。图 4-5 展示了一张示意图。
<img alt="&String s pointing at String s1" src="img/trpl04-05.svg" class="center" />
@ -37,12 +23,7 @@ fn calculate_length(s: &String) -> usize {
仔细看看这个函数调用:
```rust
# fn calculate_length(s: &String) -> usize {
# s.len()
# }
let s1 = String::from("hello");
let len = calculate_length(&s1);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:here}}
```
`&s1` 语法让我们创建一个 **指向** 值 `s1` 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。
@ -50,30 +31,19 @@ let len = calculate_length(&s1);
同理,函数签名使用 `&` 来表明参数 `s` 的类型是一个引用。让我们增加一些解释性的注释:
```rust
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用
s.len()
} // 这里s 离开了作用域。但因为它并不拥有引用值的所有权,
// 所以什么也不会发生
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-08-reference-with-annotations/src/main.rs:here}}
```
变量 `s` 有效的作用域与函数参数的作用域一样,不过当引用停止使用时并不丢弃它指向的数据,因为我们没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。
变量 `s` 有效的作用域与函数参数的作用域一样,不过当 `s` 停止使用时并不丢弃引用指向的数据,因为 `s` 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权。
我们将创建一个引用的行为称为 **借用***borrowing*)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。
我们将创建一个引用的行为称为 **借用***borrowing*)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完毕,必须还回去。我们并不拥有它。
如果我们尝试修改借用的变量呢?尝试示例 4-6 中的代码。剧透:这行不通!
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-06/src/main.rs}}
```
<span class="caption">示例 4-6尝试修改借用的值</span>
@ -81,75 +51,40 @@ fn change(some_string: &String) {
这里是错误:
```console
error[E0596]: cannot borrow immutable borrowed content `*some_string` as mutable
--> error.rs:8:5
|
7 | fn change(some_string: &String) {
| ------- use `&mut String` here to make mutable
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ cannot borrow as mutable
{{#include ../listings/ch04-understanding-ownership/listing-04-06/output.txt}}
```
正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。
### 可变引用
我们通过一个小调整就能修复示例 4-6 代码中的错误:
我们通过一个小调整就能修复示例 4-6 代码中的错误,允许我们修改一个借用的值,这就是 **可变引用***mutable reference*
<span class="filename">文件名: src/main.rs</span>
```rust
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-09-fixes-listing-04-06/src/main.rs}}
```
首先,我们必须将 `s` 改为 `mut`。然后必须在调用 `change` 函数的地方创建一个可变引用 `&mut s`,并更新函数签名以接受一个可变引用 `some_string: &mut String`。这就非常清楚地表明,`change` 函数将改变它所借用的值。
首先,我们必须将 `s` 改为 `mut`。然后在调用 `change` 函数的地方创建一个可变引用 `&mut s`,并更新函数签名以接受一个可变引用 `some_string: &mut String`。这就非常清楚地表明,`change` 函数将改变它所借用的值。
不过可变引用有一个很大的限制:在同一时间只能有一个对某一特定数据的可变引用。这些代码会失败:
可变引用有一个很大的限制:在同一时间只能有一个对某一特定数据的可变引用。这些尝试创建两个 `s` 的可变引用的代码会失败:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/src/main.rs:here}}
```
错误如下:
```console
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here
For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error
{{#include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/output.txt}}
```
这个报错说这段代码是无效的,因为我们不能在同一时间多次将 `s` 作为可变变量借用。第一个可变的借入在 `r1` 中,并且必须持续到在 `println` 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 `r2` 中创建另一个可变引用,该引用借用与 `r1` 相同的数据。
防止同一时间对同一数据进行多个可变引用的限制允许可变性,不过是以一种受限制的方式允许。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。
这个限制的好处是 Rust 可以在编译时就避免数据竞争。**数据竞争***data race*)类似于竞态条件,它可由这三个行为造成:
防止同一时间对同一数据进行多个可变引用的限制允许可变性,不过是以一种受限制的方式允许。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。**数据竞争***data race*)类似于竞态条件,它可由这三个行为造成:
* 两个或更多指针同时访问同一数据。
* 至少有一个指针被用来写入数据。
@ -160,66 +95,30 @@ error: could not compile `ownership` due to previous error
一如既往,可以使用大括号来创建一个新的作用域,以允许拥有多个可变引用,只是不能 **同时** 拥有:
```rust
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-11-muts-in-separate-scopes/src/main.rs:here}}
```
类似的规则也存在于同时使用可变与不可变引用中。这些代码会导致一个错误:
Rust 在同时使用可变与不可变引用时也采用的类似的规则。这些代码会导致一个错误:
```rust,ignore,does_not_compile
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题
println!("{}, {}, and {}", r1, r2, r3);
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/src/main.rs:here}}
```
错误如下:
```console
$ cargo run
Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // no problem
| -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error
{{#include ../listings/ch04-understanding-ownership/no-listing-12-immutable-and-mutable-not-allowed/output.txt}}
```
哇哦!我们 **也** 不能在拥有不可变引用的同时拥有可变引用。不可变引用的用户可不希望在他们的眼皮底下值就被意外的改变了!然而,多个不可变引用是可以的,因为没有哪个只能读取数据的人有能力影响其他人读取到的数据。
注意一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。例如,因为最后一次使用不可变引用(`println!`),发生在声明可变引用之前,所以如下代码是可以编译的:
```rust,ignore
let mut s = String::from("hello");
let r1 = &s; // 没问题
let r2 = &s; // 没问题
println!("{} and {}", r1, r2);
// 此位置之后 r1 和 r2 不再使用
let r3 = &mut s; // 没问题
println!("{}", r3);
```rust,edition2021
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-13-reference-scope-ends/src/main.rs:here}}
```
不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这也是创建可变引用 `r3` 的地方。它们的作用域没有重叠所以代码是可以编译的。编译器在作用域结束之前判断不再使用的引用的能力被称为非词法作用域生命周期Non-Lexical Lifetimes简称NLL。你可以在 [The Edition Guide][nll] 中阅读更多关于它的信息。
不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这也是创建可变引用 `r3` 的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器在作用域结束之前判断不再使用的引用的能力被称为 **非词法作用域生命周期***Non-Lexical Lifetimes*,简称 NLL。你可以在 [The Edition Guide][nll] 中阅读更多关于它的信息。
尽管这些错误有时使人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug在编译时而不是在运行时并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。
@ -232,35 +131,20 @@ println!("{}", r3);
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/src/main.rs}}
```
这里是错误:
```console
error[E0106]: missing lifetime specifier
--> main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is
no value for it to be borrowed from
= help: consider giving it a 'static lifetime
{{#include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/output.txt}}
```
错误信息引用了一个我们还未介绍的功能生命周期lifetimes。第十章会详细介绍生命周期。不过如果你不理会生命周期部分错误信息中确实包含了为什么这段代码有问题的关键信息
```console
this function's return type contains a borrowed value, but there is no value for it to be borrowed from.
```text
this function's return type contains a borrowed value, but there is no value
for it to be borrowed from
```
让我们仔细看看我们的 `dangle` 代码的每一步到底发生了什么:
@ -268,13 +152,7 @@ this function's return type contains a borrowed value, but there is no value for
<span class="filename">文件名: src/main.rs</span>
```rust,ignore,does_not_compile
fn dangle() -> &String { // dangle 返回一个字符串的引用
let s = String::from("hello"); // s 是一个新字符串
&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-15-dangling-reference-annotated/src/main.rs:here}}
```
因为 `s` 是在 `dangle` 函数内创建的,当 `dangle` 的代码执行完毕后,`s` 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 `String`这可不对Rust 不会允许我们这么做。
@ -282,11 +160,7 @@ fn dangle() -> &String { // dangle 返回一个字符串的引用
这里的解决方法是直接返回 `String`
```rust
fn no_dangle() -> String {
let s = String::from("hello");
s
}
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-16-no-dangle/src/main.rs:here}}
```
这样就没有任何错误了。所有权被移动出去,所以没有值被释放。

Loading…
Cancel
Save