update to ch04-03

main
KaiserY 4 days ago
parent ca9cdb77b3
commit 9bf7ebf815

@ -252,5 +252,5 @@ again!
当你准备好继续时,我们将讨论一个在其他编程语言中**并不**常见的概念所有权ownership 当你准备好继续时,我们将讨论一个在其他编程语言中**并不**常见的概念所有权ownership
[comparing-the-guess-to-the-secret-number]: ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number [comparing-the-guess-to-the-secret-number]: ch02-00-guessing-game-tutorial.html#比较猜测的数字和秘密数字
[quitting-after-a-correct-guess]: ch02-00-guessing-game-tutorial.html#quitting-after-a-correct-guess [quitting-after-a-correct-guess]: ch02-00-guessing-game-tutorial.html#猜测正确后退出

@ -1,7 +1,6 @@
## 什么是所有权? ## 什么是所有权?
<!-- https://github.com/rust-lang/book/blob/main/src/ch04-01-what-is-ownership.md --> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/05d114287b7d6f6c9253d5242540f00fbd6172ab/src/ch04-01-what-is-ownership.md)
<!-- commit f8ed2ced5daaa26e2b3a69df4bf5e1ce04dda758 -->
**所有权***ownership*)是 Rust 用于如何管理内存的一组规则。所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制在程序运行时有规律地寻找不再使用的内存在另一些语言中程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序的运行。 **所有权***ownership*)是 Rust 用于如何管理内存的一组规则。所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制在程序运行时有规律地寻找不再使用的内存在另一些语言中程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序的运行。
@ -66,7 +65,11 @@ let s = "hello";
我们已经见过字符串字面值即被硬编码进程序里的字符串值。字符串字面值是很方便的不过它们并不适合使用文本的每一种场景。原因之一就是它们是不可变的。另一个原因是并非所有字符串的值都能在编写代码时就知道例如要是想获取用户输入并存储该怎么办呢为此Rust 有另一种字符串类型,`String`。这个类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。可以使用 `from` 函数基于字符串字面值来创建 `String`,如下: 我们已经见过字符串字面值即被硬编码进程序里的字符串值。字符串字面值是很方便的不过它们并不适合使用文本的每一种场景。原因之一就是它们是不可变的。另一个原因是并非所有字符串的值都能在编写代码时就知道例如要是想获取用户输入并存储该怎么办呢为此Rust 有另一种字符串类型,`String`。这个类型管理被分配到堆上的数据,所以能够存储在编译时未知大小的文本。可以使用 `from` 函数基于字符串字面值来创建 `String`,如下:
这两个冒号 `::` 是运算符,允许将特定的 `from` 函数置于 `String` 类型的命名空间namespace而不需要使用类似 `string_from` 这样的名字。在第五章的 [“方法语法”“Method Syntax”][method-syntax] 部分会着重讲解这个语法,而且在第七章的 [“路径用于引用模块树中的项”][paths-module-tree] 中会讲到模块的命名空间。 ```rust
let s = String::from("hello");
```
这两个冒号 `::` 是运算符,允许将特定的 `from` 函数置于 `String` 类型的命名空间namespace而不需要使用类似 `string_from` 这样的名字。在第五章的 [“方法”][methods] 部分会更详细地讲解这个语法,而且在第七章的 [“路径用于引用模块树中的项”][paths-module-tree] 中会讲到模块的命名空间。
```rust ```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-01-can-mutate-string/src/main.rs:here}} {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-01-can-mutate-string/src/main.rs:here}}
@ -223,7 +226,7 @@ style="width: 50%;"
原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 `y` 后使 `x` 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 `clone` 并不会与通常的浅拷贝有什么不同,我们可以不用管它。 原因是像整型这样的在编译时已知大小的类型被整个存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 `y` 后使 `x` 无效。换句话说,这里没有深浅拷贝的区别,所以这里调用 `clone` 并不会与通常的浅拷贝有什么不同,我们可以不用管它。
Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上([第十章][ch10]将会详细讲解 trait。如果一个类型实现了 `Copy` trait那么一个旧的变量在将其赋值给其他变量后仍然有效。 Rust 有一个叫做 `Copy` trait 的特殊注解,可以用在类似整型这样的存储在栈上的类型上([第十章][traits]将会详细讲解 trait。如果一个类型实现了 `Copy` trait那么一个旧的变量在将其赋值给其他变量后仍然有效。
Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Copy` trait。如果我们对其值离开作用域时需要特殊处理的类型使用 `Copy` 注解,将会出现一个编译时错误。要学习如何为你的类型添加 `Copy` 注解以实现该 trait请阅读附录 C 中的 [“可派生的 trait”][derivable-traits]。 Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Copy` trait。如果我们对其值离开作用域时需要特殊处理的类型使用 `Copy` 注解,将会出现一个编译时错误。要学习如何为你的类型添加 `Copy` 注解以实现该 trait请阅读附录 C 中的 [“可派生的 trait”][derivable-traits]。
@ -279,8 +282,8 @@ Rust 不允许自身或其任何部分实现了 `Drop` trait 的类型使用 `Co
[data-types]: ch03-02-data-types.html#数据类型 [data-types]: ch03-02-data-types.html#数据类型
[ch8]: ch08-02-strings.html [ch8]: ch08-02-strings.html
[ch10]: ch10-00-generics.html
[derivable-traits]: appendix-03-derivable-traits.html [derivable-traits]: appendix-03-derivable-traits.html
[method-syntax]: ch05-03-method-syntax.html#方法语法 [methods]: ch05-03-method-syntax.html#方法
[paths-module-tree]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html [paths-module-tree]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html
[traits]: ch10-02-traits.html
[drop]: https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop [drop]: https://doc.rust-lang.org/std/ops/trait.Drop.html#tymethod.drop

@ -1,9 +1,8 @@
## 引用与借用 ## 引用与借用
<!-- https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md --> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/bb86b1763bdfb823e3e1d52c57020543b0fc7c4a/src/ch04-02-references-and-borrowing.md)
<!-- commit b8b94b3d93ddd5186efa079913a78cb49a679a13 -->
示例 4-5 中的元组代码有这样一个问题:我们必须`String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。相反我们可以提供一个 `String` 值的引用reference。**引用***reference*)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。与指针不同,引用在其生命周期内保证指向某个特定类型的有效值。 示例 4-5 中的元组代码有这样一个问题:我们必须`String` 返回给调用函数,这样在调用 `calculate_length` 之后仍然能使用它,因为 `String` 已经被移动进了 `calculate_length`。另一种做法是提供 `String` 值的引用reference。**引用***reference*)有点像指针,因为它是一个地址,我们可以沿着它访问存储在该地址中的数据,而这些数据归其他变量所有。与指针不同,引用在其生命周期内保证指向某个特定类型的有效值。
下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权: 下面是如何定义并使用一个(新的)`calculate_length` 函数,它以一个对象的引用作为参数而不是获取值的所有权:
@ -13,7 +12,7 @@
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:all}} {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:all}}
``` ```
首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 `&s1``calculate_length`,同时在函数定义中,我们获取 `&String` 而不是 `String`。这些 & 符号就是 **引用**,它们允许你使用值但不获取其所有权。图 4-6 展示了一张示意图 首先,注意变量声明和函数返回值中的元组代码都消失了。其次,注意我们把 `&s1` 传给 `calculate_length`,而在函数定义中,我们接收的是 `&String` 而不是 `String`。这些 `&` 符号表示 **引用**;它们让你引用某个值而不取得它的所有权。图 4-6 展示了这一概念
<img alt="Three tables: the table for s contains only a pointer to the table <img alt="Three tables: the table for s contains only a pointer to the table
for s1. The table for s1 contains the stack data for s1 and points to the for s1. The table for s1 contains the stack data for s1 and points to the
@ -29,7 +28,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:here}} {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-07-reference/src/main.rs:here}}
``` ```
`&s1` 语法让我们创建一个**指向**值 `s1` 的引用,但是并不拥有它。因为并不拥有这个值,所以当引用停止使用时,它所指向的值也不会被丢弃。 `&s1` 语法让我们创建一个**指向**值 `s1` 的引用,但并不拥有它。因为这个引用并不拥有该值,所以当引用停止使用时,它所指向的值也不会被丢弃。
同理,函数签名使用 `&` 来表明参数 `s` 的类型是一个引用。让我们增加一些解释性的注释: 同理,函数签名使用 `&` 来表明参数 `s` 的类型是一个引用。让我们增加一些解释性的注释:
@ -37,7 +36,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-08-reference-with-annotations/src/main.rs:here}} {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-08-reference-with-annotations/src/main.rs:here}}
``` ```
变量 `s` 有效的作用域与函数参数的作用域一样,不过当 `s` 停止使用时并不丢弃引用指向的数据,因为 `s` 并没有所有权。当函数使用引用而不是实际值作为参数,无需返回值来交还所有权,因为就不曾拥有所有权 变量 `s` 的有效作用域与其他函数参数相同,不过当 `s` 停止使用时,它所指向的值不会被丢弃,因为 `s` 并不拥有它。当函数把引用而不是实际值作为参数时,就不需要通过返回值来交还所有权,因为函数从未拥有过它
我们将创建一个引用的行为称为 **借用***borrowing*)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完后,必须还回去。因为我们并不拥有它的所有权。 我们将创建一个引用的行为称为 **借用***borrowing*)。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来。当你使用完后,必须还回去。因为我们并不拥有它的所有权。
@ -57,7 +56,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#include ../listings/ch04-understanding-ownership/listing-04-06/output.txt}} {{#include ../listings/ch04-understanding-ownership/listing-04-06/output.txt}}
``` ```
正如变量默认是不可变的,引用也一样。(默认)不允许修改引用的值。 正如变量默认是不可变的,引用默认也是不可变的。我们不允许通过引用修改它指向的值。
### 可变引用 ### 可变引用
@ -69,7 +68,7 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-09-fixes-listing-04-06/src/main.rs}} {{#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` 的可变引用的代码会失败: 可变引用有一个很大的限制:如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。这些尝试创建两个 `s` 的可变引用的代码会失败:
@ -85,13 +84,13 @@ string data on the heap." src="img/trpl04-06.svg" class="center" />
{{#include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/output.txt}} {{#include ../listings/ch04-understanding-ownership/no-listing-10-multiple-mut-not-allowed/output.txt}}
``` ```
这个报错说这段代码是无效的,因为我们不能在同一时间多次将 `s` 作为可变变量借用。第一个可变的借入在 `r1` 中,并且必须持续到在 `println!` 中使用它,但是在那个可变引用的创建和它的使用之间,我们又尝试在 `r2` 中创建另一个可变引用,该引用借用与 `r1` 相同的数据。 这个报错说明这段代码无效,因为我们不能在同一时间多次以可变方式借用 `s`。第一个可变借用在 `r1` 中,并且必须持续到它在 `println!` 中被使用;但在这个可变引用被创建和被使用之间,我们又尝试在 `r2` 中创建另一个可变引用,它借用的是和 `r1` 相同的数据。
这一限制以一种非常小心谨慎的方式允许可变性,防止同一时间对同一数据存在多个可变引用。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。这个限制的好处是 Rust 可以在编译时就避免数据竞争。**数据竞争***data race*)类似于竞态条件,它可由这三个行为造成 这一限制让可变性以一种受到严格控制的方式出现,从而防止在同一时间对同一数据存在多个可变引用。刚接触 Rust 的人往往不太适应这一点,因为大多数语言都允许你随时修改变量。这个限制的好处是 Rust 可以在编译时防止数据竞争。**数据竞争***data race*)类似于竞态条件,它会在以下三种行为同时发生时出现
* 两个或更多指针同时访问同一数据。 - 两个或更多指针同时访问同一数据。
* 至少有一个指针被用来写入数据。 - 至少有一个指针被用来写入数据。
* 没有同步数据访问的机制。 - 没有同步数据访问的机制。
数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 通过拒绝编译存在数据竞争的代码来避免此问题! 数据竞争会导致未定义行为难以在运行时追踪并且难以诊断和修复Rust 通过拒绝编译存在数据竞争的代码来避免此问题!
@ -125,11 +124,11 @@ Rust 在同时使用可变与不可变引用时也强制采用类似的规则。
不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这发生在可变引用 `r3` 被创建之前。因为它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。 不可变引用 `r1``r2` 的作用域在 `println!` 最后一次使用之后结束,这发生在可变引用 `r3` 被创建之前。因为它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。
尽管借用错误有时令人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug在编译时而不是在运行时并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。 尽管借用错误有时令人沮丧,但请记住,这是 Rust 编译器在提前指出一个潜在 bug并且精确告诉你问题出在哪里而且这一切发生在编译时而不是运行时。这样你就不必再去追查为什么数据的状态和你原先想的不一样。
### 悬垂引用Dangling References ### 悬垂引用Dangling References
具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个**悬垂指针***dangling pointer*)—— 指向可能已被分配给其他用途的内存位置的指针。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂引用:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。 带有指针的语言中,如果释放了一块内存,却保留了指向它的指针,就很容易错误地制造出一个**悬垂指针***dangling pointer*):这个指针指向的内存位置可能已经被分配作其他用途。相比之下,在 Rust 中,编译器保证引用永远不会变成悬垂引用:如果你持有某些数据的引用,编译器会确保这些数据不会在它们的引用之前离开作用域。
让我们尝试创建一个悬垂引用,看看 Rust 如何通过一个编译时错误来防止它: 让我们尝试创建一个悬垂引用,看看 Rust 如何通过一个编译时错误来防止它:
@ -145,7 +144,7 @@ Rust 在同时使用可变与不可变引用时也强制采用类似的规则。
{{#include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/output.txt}} {{#include ../listings/ch04-understanding-ownership/no-listing-14-dangling-reference/output.txt}}
``` ```
错误信息引用了一个我们还未介绍的功能生命周期lifetimes。第十章会详细介绍生命周期。不过如果你不理会生命周期部分错误信息中确实包含了为什么这段代码有问题的关键信息: 这条错误信息提到了一个我们还没有讲到的特性生命周期lifetimes。第十章会详细讨论生命周期。不过即使先不理会和生命周期相关的部分这条错误信息里也已经包含了说明这段代码为何有问题的关键信息:
```text ```text
this function's return type contains a borrowed value, but there is no value this function's return type contains a borrowed value, but there is no value
@ -160,7 +159,7 @@ for it to be borrowed from
{{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-15-dangling-reference-annotated/src/main.rs:here}} {{#rustdoc_include ../listings/ch04-understanding-ownership/no-listing-15-dangling-reference-annotated/src/main.rs:here}}
``` ```
因为 `s` 是在 `dangle` 函数内创建的,当 `dangle` 的代码执行完毕后,`s` 将被释放。不过我们尝试返回它的引用。这意味着这个引用会指向一个无效的 `String`这可不对Rust 不会允许我们这么做。 因为 `s` 是在 `dangle` 函数内创建的,所以`dangle` 的代码执行完毕后,`s` 就会被释放。但我们却尝试返回对它的引用。这意味着这个引用将指向一个无效的 `String`这显然不对Rust 不允许我们这么做。
这里的解决方法是直接返回 `String` 这里的解决方法是直接返回 `String`

@ -1,21 +1,20 @@
## Slice 类型 ## Slice 类型
<!-- https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md --> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/8a6130451b0817ead5c2522ce641dcb0f11a8571/src/ch04-03-slices.md)
<!-- commit f8ed2ced5daaa26e2b3a69df4bf5e1ce04dda758 -->
**切片***slice*允许你引用集合中一段连续的元素序列而不用引用整个集合。slice 是一种引用,所以它不拥有所有权。 **切片***slice*允许你引用集合中一段连续的元素序列而不用引用整个集合。slice 是一种引用,所以它不拥有所有权。
这里有一个编程小习题:编写一个函数,该函数接收一个用空格分隔单词的字符串,并返回在该字符串中找到的第一个单词。如果函数在该字符串中并未找到空格,则整个字符串就是一个单词,所以应该返回整个字符串。 这里有一个编程小习题:编写一个函数,接收一个由空格分隔单词的字符串,并返回它在该字符串中找到的第一个单词。如果函数在该字符串中没有找到空格,那么整个字符串就是一个单词,因此应该返回整个字符串。
> 注意:出于介绍字符串 slice 的目的,本小节假设只使用 ASCII 字符集;一个关于 UTF-8 处理的更全面的讨论位于第八章[“使用字符串储存 UTF-8 编码的文本”][strings]小节。 > 注意:为了介绍字符串 slice本小节假设只处理 ASCII关于 UTF-8 处理的更完整讨论,请见第八章的[“使用字符串储存 UTF-8 编码的文本”][strings]一节。
让我们推敲下如何不用 slice 编写这个函数的签名,来理解 slice 能解决的问题: 让我们先想想,如果不用 slice该怎样写这个函数的签名从而理解 slice 解决了什么问题:
```rust,ignore ```rust,ignore
fn first_word(s: &String) -> ? fn first_word(s: &String) -> ?
``` ```
`first_word` 函数有一个参数 `&String`。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们并没有一个真正获取**部分**字符串的办法。不过,我们可以返回单词结尾的索引,结尾由一个空格表示。试试如示例 4-7 中的代码。 `first_word` 函数有一个 `&String` 类型的参数。因为我们不需要所有权,所以这没有问题。不过应该返回什么呢?我们其实没有办法真正表示字符串的**一部分**。不过,我们可以返回单词结尾的索引,也就是空格所在的位置。试试示例 4-7 中的代码。
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
@ -25,7 +24,7 @@ fn first_word(s: &String) -> ?
<span class="caption">示例 4-7`first_word` 函数返回 `String` 参数的一个字节索引值</span> <span class="caption">示例 4-7`first_word` 函数返回 `String` 参数的一个字节索引值</span>
因为需要逐个元素的检查 `String` 中的值是否为空格,需要用 `as_bytes` 方法将 `String` 转化为字节数组。 因为我们需要逐个检查 `String` 中的元素是否为空格,所以要用 `as_bytes` 方法把 `String` 转换成字节数组。
```rust,ignore ```rust,ignore
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:as_bytes}} {{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:as_bytes}}
@ -37,9 +36,9 @@ fn first_word(s: &String) -> ?
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:iter}} {{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:iter}}
``` ```
我们将在[第十三章][ch13]详细讨论迭代器。现在,只需知道 `iter` 方法返回集合中的每一个元素,而 `enumerate` 包装了 `iter` 的结果,将这些元素作为元组的一部分来返回。`enumerate` 返回的元组中,第一个元素是索引,第二个元素是集合中元素的引用。这比我们自己计算索引要方便一些。 我们会在[第十三章][ch13]更详细地讨论迭代器。现在只需要知道,`iter` 方法会返回集合中的每个元素,而 `enumerate` 会包装 `iter` 的结果,把每个元素作为元组的一部分返回。`enumerate` 返回的元组中,第一个元素是索引,第二个元素是该元素的引用。这比我们自己手动计算索引更方便一些。
因为 `enumerate` 方法返回一个元组,我们可以使用模式来解构,我们将在[第六章][ch6]中进一步讨论有关模式的问题。所以在 `for` 循环中,我们指定了一个模式,其中元组中的 `i` 是索引而元组中的 `&item` 是单个字节。因为我们从 `.iter().enumerate()` 中获取了集合元素的引用,所以模式中使用了 `&` 因为 `enumerate` 方法返回的是元组,所以我们可以用模式来解构它;我们会在[第六章][ch6]进一步讨论模式。因此,在 `for` 循环中,我们指定了一个模式,其中元组里的 `i` 是索引,元组里的 `&item` 是单个字节。因为我们从 `.iter().enumerate()` 拿到的是元素的引用,所以模式中用了 `&`
`for` 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 `s.len()` 返回字符串的长度。 `for` 循环中,我们通过字节的字面值语法来寻找代表空格的字节。如果找到了一个空格,返回它的位置。否则,使用 `s.len()` 返回字符串的长度。
@ -57,7 +56,7 @@ fn first_word(s: &String) -> ?
<span class="caption">示例 4-8存储 `first_word` 函数调用的返回值并接着改变 `String` 的内容</span> <span class="caption">示例 4-8存储 `first_word` 函数调用的返回值并接着改变 `String` 的内容</span>
这个程序编译时没有任何错误,而且在调用 `s.clear()` 之后使用 `word` 也不会出错。因为 `word``s` 状态完全没有联系,所以 `word ` 仍然包含值 `5`。可以尝试用值 `5` 来提取变量 `s` 的第一个单词,不过这是有 bug 的,因为在我们将 `5` 保存到 `word` 之后 `s` 的内容已经改变 这个程序编译时不会报任何错误,而且即使在调用 `s.clear()` 之后再使用 `word`,也同样不会报错。因为 `word``s` 的状态完全没有关联,所以 `word` 仍然包含值 `5`。我们可以尝试用这个值 `5` 从变量 `s` 中提取第一个单词,但这会出 bug因为在把 `5` 保存进 `word` 之后,`s` 的内容已经变了
我们不得不时刻担心 `word` 的索引与 `s` 中的数据不再同步,这既繁琐又易出错!如果编写这么一个 `second_word` 函数的话,管理索引这件事将更加容易出问题。它的签名看起来像这样: 我们不得不时刻担心 `word` 的索引与 `s` 中的数据不再同步,这既繁琐又易出错!如果编写这么一个 `second_word` 函数的话,管理索引这件事将更加容易出问题。它的签名看起来像这样:
@ -79,7 +78,7 @@ fn second_word(s: &String) -> (usize, usize) {
不同于整个 `String` 的引用,`hello` 是一个部分 `String` 的引用,由一个额外的 `[0..5]` 部分指定。可以使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 索引 6 的指针和长度值 5 的 slice。 不同于整个 `String` 的引用,`hello` 是一个部分 `String` 的引用,由一个额外的 `[0..5]` 部分指定。可以使用一个由中括号中的 `[starting_index..ending_index]` 指定的 range 创建一个 slice其中 `starting_index` 是 slice 的第一个位置,`ending_index` 则是 slice 最后一个位置的后一个值。在其内部slice 的数据结构存储了 slice 的开始位置和长度,长度对应于 `ending_index` 减去 `starting_index` 的值。所以对于 `let world = &s[6..11];` 的情况,`world` 将是一个包含指向 `s` 索引 6 的指针和长度值 5 的 slice。
图 4-7 展示了一个图例 图 4-7 给出了示意图
<img alt="Three tables: a table representing the stack data of s, which points <img alt="Three tables: a table representing the stack data of s, which points
to the byte at index 0 in a table of the string data &quot;hello world&quot; on to the byte at index 0 in a table of the string data &quot;hello world&quot; on
@ -122,7 +121,7 @@ let slice = &s[..];
> 注意:字符串 slice range 的索引必须位于有效的 UTF-8 字符边界内,如果尝试从一个多字节字符的中间位置创建字符串 slice则程序将会因错误而退出。 > 注意:字符串 slice range 的索引必须位于有效的 UTF-8 字符边界内,如果尝试从一个多字节字符的中间位置创建字符串 slice则程序将会因错误而退出。
在记住所有这些知识后,让我们重写 `first_word`返回一个 slice。“字符串 slice” 的类型声明写作 `&str` 有了这些知识之后,让我们重写 `first_word`,让它返回一个 slice。“字符串 slice” 的类型写作 `&str`
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
@ -132,7 +131,7 @@ let slice = &s[..];
我们使用跟示例 4-7 相同的方式获取单词结尾的索引,通过寻找第一个出现的空格。当找到一个空格,我们返回一个字符串 slice它使用字符串的开始和空格的索引作为开始和结束的索引。 我们使用跟示例 4-7 相同的方式获取单词结尾的索引,通过寻找第一个出现的空格。当找到一个空格,我们返回一个字符串 slice它使用字符串的开始和空格的索引作为开始和结束的索引。
现在当调用 `first_word` 时,会返回与底层数据关联的单个值。这个值由一个 slice 开始位置的引用和 slice 中元素的数量组成。 现在调用 `first_word` 时,它会返回一个与底层数据绑定在一起的值。这个值由一个指向 slice 起始位置的引用和 slice 中元素的数量组成。
`second_word` 函数也可以改为返回一个 slice `second_word` 函数也可以改为返回一个 slice
@ -140,7 +139,7 @@ let slice = &s[..];
fn second_word(s: &String) -> &str { fn second_word(s: &String) -> &str {
``` ```
现在我们有了一个不易混淆且直观的 API 了,因为编译器会确保指向 `String` 的引用持续有效。还记得示例 4-8 程序中,那个当我们获取第一个单词结尾的索引后,接着就清除了字符串导致索引就无效的 bug 吗那些代码在逻辑上是不正确的但却没有显示任何直接的错误。问题会在之后尝试对空字符串使用第一个单词的索引时出现。slice 就不可能出现这种 bug 并让我们更早的知道出问题了。使用 slice 版本的 `first_word` 会抛出一个编译时错误: 现在我们有了一个更直观、也更不容易出错的 API因为编译器会确保指向 `String` 的引用始终有效。还记得示例 4-8 里的那个 bug 吗我们先拿到了第一个单词结尾的索引然后又清空了字符串于是索引失效了。那段代码在逻辑上是错的但当时却不会直接报错。问题会在之后你尝试对一个已被清空的字符串继续使用那个索引时才暴露出来。slice 让这种 bug 不再可能发生,并且会更早告诉我们代码出了问题。使用 slice 版本的 `first_word` 会得到一个编译时错误:
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
@ -182,7 +181,7 @@ fn first_word(s: &String) -> &str {
<span class="caption">示例 4-9: 通过将 `s` 参数的类型改为字符串 slice 来改进 `first_word` 函数</span> <span class="caption">示例 4-9: 通过将 `s` 参数的类型改为字符串 slice 来改进 `first_word` 函数</span>
如果有一个字符串 slice可以直接传递它。如果有一个 `String`,则可以传递整个 `String` 的 slice 或对 `String` 的引用。这种灵活性利用了 *deref coercions* 的优势,这个特性我们将在[“函数和方法的隐式 Deref 强制转换”][deref-coercions]章节中介绍。定义一个获取字符串 slice 而不是 `String` 引用的函数使得我们的 API 更加通用并且不会丢失任何功能: 如果我们有一个字符串 slice就可以直接把它传进去。如果我们有一个 `String`,也可以传入整个 `String` 的 slice或者传入对 `String` 的引用。这种灵活性利用了 *deref coercions*,也就是我们会在[“在函数和方法中使用 Deref 强制转换”][deref-coercions]一节中讲到的特性。把函数参数定义为字符串 slice而不是 `String` 的引用,会让我们的 API 更通用、更有用,而且不会损失任何功能:
<span class="filename">文件名src/main.rs</span> <span class="filename">文件名src/main.rs</span>
@ -219,4 +218,4 @@ assert_eq!(slice, &[2, 3]);
[ch13]: ch13-02-iterators.html [ch13]: ch13-02-iterators.html
[ch6]: ch06-02-match.html#绑定值的模式 [ch6]: ch06-02-match.html#绑定值的模式
[strings]: ch08-02-strings.html#使用字符串储存-utf-8-编码的文本 [strings]: ch08-02-strings.html#使用字符串储存-utf-8-编码的文本
[deref-coercions]: ch15-02-deref.html#函数和方法的隐式-deref-强制转换 [deref-coercions]: ch15-02-deref.html#在函数和方法中使用-deref-强制转换

@ -1,4 +1,4 @@
## 方法语法 ## 方法
<!-- https://github.com/rust-lang/book/blob/main/src/ch05-03-method-syntax.md --> <!-- https://github.com/rust-lang/book/blob/main/src/ch05-03-method-syntax.md -->
<!-- commit 6b65e9500535f85fad186c6f0e67a927863e454d --> <!-- commit 6b65e9500535f85fad186c6f0e67a927863e454d -->

@ -109,7 +109,7 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行普通解引用
注意,每次当我们在代码中使用 `*` 时, `*` 运算符都被替换成了先调用 `deref` 方法再接着使用 `*` 解引用的操作,且只会发生一次,不会对 `*` 操作符无限递归替换,解引用出上面 `i32` 类型的值就停止了,这个值与示例 15-9 中 `assert_eq!``5` 相匹配。 注意,每次当我们在代码中使用 `*` 时, `*` 运算符都被替换成了先调用 `deref` 方法再接着使用 `*` 解引用的操作,且只会发生一次,不会对 `*` 操作符无限递归替换,解引用出上面 `i32` 类型的值就停止了,这个值与示例 15-9 中 `assert_eq!``5` 相匹配。
### 函数和方法的隐式 Deref 强制转换 ### 在函数和方法中使用 Deref 强制转换
**Deref 强制转换**_deref coercions_将实现了 `Deref` trait 的类型的引用转换为另一种类型的引用。例如Deref 强制转换可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref` trait 因此可以返回 `&str`。Deref 强制转换是 Rust 在函数或方法传参上的一种便利操作,并且只能作用于实现了 `Deref` trait 的类型。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。 **Deref 强制转换**_deref coercions_将实现了 `Deref` trait 的类型的引用转换为另一种类型的引用。例如Deref 强制转换可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref` trait 因此可以返回 `&str`。Deref 强制转换是 Rust 在函数或方法传参上的一种便利操作,并且只能作用于实现了 `Deref` trait 的类型。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。

Loading…
Cancel
Save