mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
update to ch05-03
This commit is contained in:
parent
fbdc47c405
commit
23673cb6e7
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch02-00-guessing-game-tutorial.md](https://github.com/rust-lang/book/blob/main/src/ch02-00-guessing-game-tutorial.md)
|
||||
> <br>
|
||||
> commit 6e2fe7c0f085989cc498cec139e717e2af172cb7
|
||||
> commit f28554d1b216d49a4d11d05995302cf54a0f9d72
|
||||
|
||||
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中一些常用概念,并通过真实的程序来展示如何运用它们。你将会学到 `let`、`match`、方法(method)、关联函数(associated function)、外部 crate 等知识!后续章节会深入探讨这些概念的细节。在这一章,我们将练习基础内容。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/main/src/ch03-00-common-programming-concepts.md)
|
||||
> <br>
|
||||
> commit 05d9c4c2312a6542f792492d17a62f79ad6dfd7b
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。很多编程语言的核心概念都是共通的,本章中展示的概念都不是 Rust 所特有的,不过我们会在 Rust 上下文中讨论它们,并解释使用这些概念的惯例。
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 变量和可变性
|
||||
|
||||
> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch03-01-variables-and-mutability.md)
|
||||
>
|
||||
> commit 54164e99f7a1ad27fc6fc578783994513abd988d
|
||||
>
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
正如第二章中[“使用变量储存值”][storing-values-with-variables]<!-- ignore --> 部分提到的那样,变量默认是不可改变的(immutable)。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择不使用不可变性。
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/src/main.rs}}
|
||||
```
|
||||
|
||||
保存并使用 `cargo run` 运行程序。应该会看到一条错误信息,如下输出所示:
|
||||
保存并使用 `cargo run` 运行程序。应该会看到一条与不可变性有关的错误信息,如下输出所示:
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/output.txt}}
|
||||
@ -30,7 +30,7 @@
|
||||
|
||||
Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。
|
||||
|
||||
不过可变性也是非常有用的,可以用来更方便地编写代码。尽管变量默认是不可变的,你仍然可以在变量名前添加 `mut` 来使其可变,正如在第二章所做的那样。`mut` 也向读者表明了其他代码将会改变这个变量值的意图。
|
||||
不过可变性也是非常有用的,可以用来更方便地编写代码。尽管变量默认是不可变的,你仍然可以在变量名前添加 `mut` 来使其可变,正如在[第二章][storing-values-with-variables]所做的那样。`mut` 也向读者表明了其他代码将会改变这个变量值的意图。
|
||||
|
||||
例如,让我们将 *src/main.rs* 修改为如下代码:
|
||||
|
||||
@ -52,9 +52,7 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
|
||||
|
||||
类似于不可变变量,*常量(constants)* 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
|
||||
|
||||
首先,不允许对常量使用 `mut`。常量不光默认不能变,它总是不能变。
|
||||
|
||||
声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,[“数据类型”][data-types] 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
|
||||
首先,不允许对常量使用 `mut`。常量不光默认不可变,它总是不可变。声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,[“数据类型”][data-types] 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
|
||||
|
||||
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/main/src/ch03-02-data-types.md)
|
||||
> <br>
|
||||
> commit 4284e160715917a768d25265daf2db897c683065
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
在 Rust 中,每一个值都属于某一个 **数据类型**(*data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
let guess: u32 = "42".parse().expect("Not a number!");
|
||||
```
|
||||
|
||||
如果不像上面这样添加类型注解 `: u32`,Rust 会显示如下错误,这说明编译器需要我们提供更多信息,来了解我们想要的类型:
|
||||
如果不像上面的代码这样添加类型注解 `: u32`,Rust 会显示如下错误,这说明编译器需要我们提供更多信息,来了解我们想要的类型:
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch03-common-programming-concepts/output-only-01-no-type-annotations/output.txt}}
|
||||
@ -39,7 +39,7 @@ let guess: u32 = "42".parse().expect("Not a number!");
|
||||
| 128-bit | `i128` | `u128` |
|
||||
| arch | `isize` | `usize` |
|
||||
|
||||
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式(two’s complement representation)](https://en.wikipedia.org/wiki/Two%27s_complement) 存储。
|
||||
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式(two’s complement representation)][twos-complement] 存储。
|
||||
|
||||
每一个有符号的变体可以储存包含从 -(2<sup>n - 1</sup>) 到 2<sup>n - 1</sup> - 1 在内的数字,这里 *n* 是变体使用的位数。所以 `i8` 可以储存从 -(2<sup>7</sup>) 到 2<sup>7</sup> - 1 在内的数字,也就是从 -128 到 127。无符号的变体可以储存从 0 到 2<sup>n</sup> - 1 的数字,所以 `u8` 可以储存从 0 到 2<sup>8</sup> - 1 的数字,也就是从 0 到 255。
|
||||
|
||||
@ -63,12 +63,13 @@ let guess: u32 = "42".parse().expect("Not a number!");
|
||||
>
|
||||
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),这会导致以下两种行为之一的发生。当在 debug 模式编译时,Rust 检查这类问题并使程序 *panic*,这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
|
||||
>
|
||||
> 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码回绕(*two’s complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。依赖整型回绕被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,[`Wrapping`][wrapping]。
|
||||
> 为了显式地处理溢出的可能性,你可以使用标准库在原生数值类型上提供的以下方法:
|
||||
> - 所有模式下都可以使用 `wrapping_*` 方法进行回绕,如 `wrapping_add`
|
||||
> - 如果 `checked_*` 方法出现溢出,则返回 `None`值
|
||||
> - 用 `overflowing_*` 方法返回值和一个布尔值,表示是否出现溢出
|
||||
> - 用 `saturating_*` 方法在值的最小值或最大值处进行饱和处理
|
||||
> 使用 `--release` flag 在 release 模式中构建时,Rust **不会**检测会导致 panic 的整型溢出。相反发生整型溢出时, Rust 会进行一种被称为二进制补码 wrapping(*two’s complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。程序不会 panic,不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。
|
||||
>
|
||||
> 为了显式地处理溢出的可能性,可以使用这几类标准库提供的原始数字类型方法:
|
||||
> * 所有模式下都可以使用 `wrapping_*` 方法进行 wrapping,如 `wrapping_add`
|
||||
> * 如果 `checked_*` 方法出现溢出,则返回 `None`值
|
||||
> * 用 `overflowing_*` 方法返回值和一个布尔值,表示是否出现溢出
|
||||
> * 用 `saturating_*` 方法在值的最小值或最大值处进行饱和处理
|
||||
|
||||
#### 浮点型
|
||||
|
||||
@ -217,7 +218,7 @@ let a = [3; 5];
|
||||
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-15-invalid-array-access/src/main.rs}}
|
||||
```
|
||||
|
||||
此代码编译成功。如果您使用 `cargo run` 运行此代码并输入 0、1、2、3 或 4,程序将在数组中的索引处打印出相应的值。如果你输入一个超过数组末端的数字,如 10,你会看到这样的输出:
|
||||
此代码编译成功。如果您使用 `cargo run` 运行此代码并输入 `0`、`1`、`2`、`3` 或 `4`,程序将在数组中的索引处打印出相应的值。如果你输入一个超过数组末端的数字,如 10,你会看到这样的输出:
|
||||
|
||||
```console
|
||||
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
|
||||
@ -230,6 +231,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
[comparing-the-guess-to-the-secret-number]:
|
||||
ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number
|
||||
[twos-complement]: https://en.wikipedia.org/wiki/Two%27s_complement
|
||||
[control-flow]: ch03-05-control-flow.html#控制流
|
||||
[strings]: ch08-02-strings.html#使用字符串存储-utf-8-编码的文本
|
||||
[stack-and-heap]: ch04-01-what-is-ownership.html#the-stack-and-the-heap
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/main/src/ch03-03-how-functions-work.md)
|
||||
> <br>
|
||||
> commit 4284e160715917a768d25265daf2db897c683065
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
函数在 Rust 代码中非常普遍。你已经见过语言中最重要的函数之一:`main` 函数,它是很多程序的入口点。你也见过 `fn` 关键字,它用来声明新函数。
|
||||
|
||||
@ -70,7 +70,8 @@ Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake
|
||||
|
||||
函数体由一系列的语句和一个可选的结尾表达式构成。目前为止,我们提到的函数还不包含结尾表达式,不过你已经见过作为语句一部分的表达式。因为 Rust 是一门基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别。其他语言并没有这样的区别,所以让我们看看语句与表达式有什么区别以及这些区别是如何影响函数体的。
|
||||
|
||||
**语句**(*Statements*)是执行一些操作但不返回值的指令。表达式(*Expressions*)计算并产生一个值。让我们看一些例子。
|
||||
**语句**(*Statements*)是执行一些操作但不返回值的指令。
|
||||
**表达式**(*Expressions*)计算并产生一个值。让我们看一些例子。
|
||||
|
||||
实际上,我们已经使用过语句和表达式。使用 `let` 关键字创建变量并绑定一个值是一个语句。在列表 3-1 中,`let y = 6;` 是一个语句。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/main/src/ch03-04-comments.md)
|
||||
> <br>
|
||||
> commit d281b7b062e6dbfbcf47f8381073f7fce9e5cd4e
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
所有程序员都力求使其代码易于理解,不过有时还需要提供额外的解释。在这种情况下,程序员在源码中留下 **注释**(*comments*),编译器会忽略它们,不过阅读代码的人可能觉得有用。
|
||||
|
||||
@ -36,4 +36,6 @@
|
||||
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-25-comments-above-line/src/main.rs}}
|
||||
```
|
||||
|
||||
Rust 还有另一种注释,称为文档注释,我们将在 14 章的 “将 crate 发布到 Crates.io” 部分讨论它。
|
||||
Rust 还有另一种注释,称为文档注释,我们将在 14 章的 [“将 crate 发布到 Crates.io” ][publishing]部分讨论它。
|
||||
|
||||
[publishing]: ch14-02-publishing-to-crates-io.html
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-05-control-flow.md](https://github.com/rust-lang/book/blob/main/src/ch03-05-control-flow.md)
|
||||
> <br>
|
||||
> commit 4284e160715917a768d25265daf2db897c683065
|
||||
> commit d0acb2595c891de97a133d06635c50ab449dd65c
|
||||
|
||||
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码的能力是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
|
||||
|
||||
@ -18,9 +18,9 @@
|
||||
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-26-if-true/src/main.rs}}
|
||||
```
|
||||
|
||||
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 的值是否小于 5。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第二章 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 部分中讨论到的 `match` 表达式中的分支一样。
|
||||
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 的值是否小于 5。在条件为 `true` 时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第二章 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 部分中讨论到的 `match` 表达式中的分支一样。
|
||||
|
||||
也可以包含一个可选的 `else` 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为假时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
|
||||
也可以包含一个可选的 `else` 表达式来提供一个在条件为 `false` 时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为 `false` 时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
|
||||
|
||||
尝试运行代码,应该能看到如下输出:
|
||||
|
||||
@ -80,7 +80,7 @@
|
||||
{{#include ../listings/ch03-common-programming-concepts/no-listing-30-else-if/output.txt}}
|
||||
```
|
||||
|
||||
当执行这个程序时,它按顺序检查每个 `if` 表达式并执行第一个条件为真的代码块。注意即使 6 可以被 2 整除,也不会输出 `number is divisible by 2`,更不会输出 `else` 块中的 `number is not divisible by 4, 3, or 2`。原因是 Rust 只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
|
||||
当执行这个程序时,它按顺序检查每个 `if` 表达式并执行第一个条件为 `true` 的代码块。注意即使 6 可以被 2 整除,也不会输出 `number is divisible by 2`,更不会输出 `else` 块中的 `number is not divisible by 4, 3, or 2`。原因是 Rust 只会执行第一个条件为 `true` 的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
|
||||
|
||||
使用过多的 `else if` 表达式会使代码显得杂乱无章,所以如果有多于一个 `else if` 表达式,最好重构代码。为此,第六章会介绍一个强大的 Rust 分支结构(branching construct),叫做 `match`。
|
||||
|
||||
@ -164,7 +164,7 @@ again!
|
||||
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-33-return-value-from-loop/src/main.rs}}
|
||||
```
|
||||
|
||||
在循环之前,我们声明了一个名为 `counter` 的变量并初始化为 `0`。接着声明了一个名为 `result` 来存放循环的返回值。在循环的每一次迭代中,我们将 `counter` 变量加 `1`,接着检查计数是否等于 `10`。当相等时,使用 `break` 关键字返回值 `counter * 2`。循环之后,我们通过分号结束赋值给 `result` 的语句。最后打印出 `result` 的值,也就是 20。
|
||||
在循环之前,我们声明了一个名为 `counter` 的变量并初始化为 `0`。接着声明了一个名为 `result` 来存放循环的返回值。在循环的每一次迭代中,我们将 `counter` 变量加 `1`,接着检查计数是否等于 `10`。当相等时,使用 `break` 关键字返回值 `counter * 2`。循环之后,我们通过分号结束赋值给 `result` 的语句。最后打印出 `result` 的值,也就是 `20`。
|
||||
|
||||
#### 循环标签:在多个循环之间消除歧义
|
||||
|
||||
@ -182,7 +182,7 @@ again!
|
||||
|
||||
#### `while` 条件循环
|
||||
|
||||
在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 `break` 停止循环。这个循环类型可以通过组合 `loop`、`if`、`else` 和 `break` 来实现;如果你喜欢的话,现在就可以在程序中试试。
|
||||
在程序中计算循环的条件也很常见。当条件为 `true`,执行循环。当条件不再为 `true`,调用 `break` 停止循环。这个循环类型可以通过组合 `loop`、`if`、`else` 和 `break` 来实现;如果你喜欢的话,现在就可以在程序中试试。
|
||||
|
||||
然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 `while` 循环。示例 3-3 使用了 `while`:程序循环三次,每次数字都减一。接着,在循环结束后,打印出另一个信息并退出。
|
||||
|
||||
@ -194,7 +194,7 @@ again!
|
||||
|
||||
<span class="caption">示例 3-3: 当条件为真时,使用 `while` 循环运行代码</span>
|
||||
|
||||
这种结构消除了很多使用 `loop`、`if`、`else` 和 `break` 时所必须的嵌套,这样更加清晰。当条件为真就执行,否则退出循环。
|
||||
这种结构消除了很多使用 `loop`、`if`、`else` 和 `break` 时所必须的嵌套,这样更加清晰。当条件为 `true` 就执行,否则退出循环。
|
||||
|
||||
#### 使用 `for` 遍历集合
|
||||
|
||||
|
@ -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` 的通用函数。第五章会讨论方法语法,不过因为方法在很多语言中是一个常见功能,所以之前你可能已经见过了。
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md)
|
||||
> <br>
|
||||
> commit d82136cbdc91c598ef9997493aa577b1a349565e
|
||||
> commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed
|
||||
|
||||
示例 4-5 中的元组代码有这样一个问题:我们必须将 `String` 返回给调用函数,以便在调用 `calculate_length` 后仍能使用 `String`,因为 `String` 被移动到了 `calculate_length` 内。相反我们可以提供一个 `String` 值的引用(reference)。**引用**(*reference*)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。
|
||||
与指针不同,引用确保指向某个特定类型的有效值。
|
||||
@ -17,7 +17,9 @@
|
||||
|
||||
首先,注意变量声明和函数返回值中的所有元组代码都消失了。其次,注意我们传递 `&s1` 给 `calculate_length`,同时在函数定义中,我们获取 `&String` 而不是 `String`。这些 & 符号就是 **引用**,它们允许你使用值但不获取其所有权。图 4-5 展示了一张示意图。
|
||||
|
||||
<img alt="&String s pointing at String s1" src="img/trpl04-05.svg" class="center" />
|
||||
<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
|
||||
string data on the heap." src="img/trpl04-05.svg" class="center" />
|
||||
|
||||
<span class="caption">图 4-5:`&String s` 指向 `String s1` 示意图</span>
|
||||
|
||||
@ -123,7 +125,7 @@ Rust 在同时使用可变与不可变引用时也采用的类似的规则。这
|
||||
{{#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` 的地方。它们的作用域没有重叠,所以代码是可以编译的。编译器可以在作用域结束之前判断不再使用的引用。
|
||||
|
||||
尽管这些错误有时使人沮丧,但请牢记这是 Rust 编译器在提前指出一个潜在的 bug(在编译时而不是在运行时)并精准显示问题所在。这样你就不必去跟踪为何数据并不是你想象中的那样。
|
||||
|
||||
@ -178,5 +180,3 @@ for it to be borrowed from
|
||||
* 引用必须总是有效的。
|
||||
|
||||
接下来,我们来看看另一种不同类型的引用:slice。
|
||||
|
||||
[nll]: https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/non-lexical-lifetimes.html
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md)
|
||||
> <br>
|
||||
> commit a5e0c5b2c5f9054be3b961aea2c7edfeea591de8
|
||||
> commit 3d51f70c78162faaebcab0da0de2ddd333e7a8ed
|
||||
|
||||
*slice* 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。
|
||||
|
||||
@ -24,7 +24,7 @@ fn first_word(s: &String) -> ?
|
||||
|
||||
<span class="caption">示例 4-7:`first_word` 函数返回 `String` 参数的一个字节索引值</span>
|
||||
|
||||
因为需要逐个元素的检查 `String` 中的值是否为空格,需要用 `as_bytes` 方法将 `String` 转化为字节数组:
|
||||
因为需要逐个元素的检查 `String` 中的值是否为空格,需要用 `as_bytes` 方法将 `String` 转化为字节数组。
|
||||
|
||||
```rust,ignore
|
||||
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:as_bytes}}
|
||||
@ -80,7 +80,11 @@ fn second_word(s: &String) -> (usize, usize) {
|
||||
|
||||
图 4-6 展示了一个图例。
|
||||
|
||||
<img alt="world containing a pointer to the byte at index 6 of String s and a length 5" src="img/trpl04-06.svg" class="center" style="width: 50%;" />
|
||||
<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 "hello world" on
|
||||
the heap. The third table rep-resents the stack data of the slice world, which
|
||||
has a length value of 5 and points to byte 6 of the heap data table."
|
||||
src="img/trpl04-06.svg" class="center" style="width: 50%;" />
|
||||
|
||||
<span class="caption">图 4-6:引用了部分 `String` 的字符串 slice</span>
|
||||
|
||||
@ -151,6 +155,8 @@ fn second_word(s: &String) -> &str {
|
||||
|
||||
回忆一下借用规则,当拥有某值的不可变引用时,就不能再获取一个可变引用。因为 `clear` 需要清空 `String`,它尝试获取一个可变引用。在调用 `clear` 之后的 `println!` 使用了 `word` 中的引用,所以这个不可变的引用在此时必须仍然有效。Rust 不允许 `clear` 中的可变引用和 `word` 中的不可变引用同时存在,因此编译失败。Rust 不仅使得我们的 API 简单易用,也在编译时就消除了一整类的错误!
|
||||
|
||||
<a id="string-literals-are-slices"></a>
|
||||
|
||||
#### 字符串字面值就是 slice
|
||||
|
||||
还记得我们讲到过字符串字面值被储存在二进制文件中吗?现在知道 slice 了,我们就可以正确地理解字符串字面值了:
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-00-structs.md)
|
||||
> <br>
|
||||
> commit dd7e05275822d6cf790bcdae6983b3234141b5e7
|
||||
> commit 8612c4a5801b61f8d2e952f8bbbb444692ff1ec2
|
||||
|
||||
*struct*,或者 *structure*,是一个自定义数据类型,允许你包装和命名多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对元组和结构体进行比较和对比。
|
||||
|
||||
|
@ -2,12 +2,14 @@
|
||||
|
||||
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-01-defining-structs.md)
|
||||
> <br>
|
||||
> commit dd7e05275822d6cf790bcdae6983b3234141b5e7
|
||||
> commit a371f82b0916cf21de2d56bd386ca5d72f7699b0
|
||||
|
||||
结构体和我们在[“元组类型”][tuples]部分论过的元组类似,它们都包含多个相关的值。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
|
||||
|
||||
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字和类型,我们称为 **字段**(*field*)。例如,示例 5-1 展示了一个存储用户账号信息的结构体:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-01/src/main.rs:here}}
|
||||
```
|
||||
@ -16,6 +18,8 @@
|
||||
|
||||
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字,value 是需要存储在字段中的数据值。实例中字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-02/src/main.rs:here}}
|
||||
```
|
||||
@ -24,6 +28,8 @@
|
||||
|
||||
为了从结构体中获取某个特定的值,可以使用点号。举个例子,想要用户的邮箱地址,可以用 `user1.email`。如果结构体的实例是可变的,我们可以使用点号并为对应的字段赋值。示例 5-3 展示了如何改变一个可变的 `User` 实例中 `email` 字段的值:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-03/src/main.rs:here}}
|
||||
```
|
||||
@ -34,6 +40,8 @@
|
||||
|
||||
示例 5-4 显示了一个 `build_user` 函数,它返回一个带有给定的 email 和用户名的 `User` 结构体实例。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-04/src/main.rs:here}}
|
||||
```
|
||||
@ -44,13 +52,15 @@
|
||||
|
||||
### 使用字段初始化简写语法
|
||||
|
||||
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法**(*field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email` 和 `username` 了,如示例 5-5 所示。
|
||||
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法**(*field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `username` 和 `email` 了,如示例 5-5 所示。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-05/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 5-5:`build_user` 函数使用了字段初始化简写语法,因为 `email` 和 `username` 参数与结构体字段同名</span>
|
||||
<span class="caption">示例 5-5:`build_user` 函数使用了字段初始化简写语法,因为 `username` 和 `email` 参数与结构体字段同名</span>
|
||||
|
||||
这里我们创建了一个新的 `User` 结构体实例,它有一个叫做 `email` 的字段。我们想要将 `email` 字段的值设置为 `build_user` 函数 `email` 参数的值。因为 `email` 字段与 `email` 参数有着相同的名称,则只需编写 `email` 而不是 `email: email`。
|
||||
|
||||
@ -60,6 +70,8 @@
|
||||
|
||||
首先,示例 5-6 展示了不使用更新语法时,如何在 `user2` 中创建一个新 `User` 实例。我们为 `email` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-06/src/main.rs:here}}
|
||||
```
|
||||
@ -68,6 +80,8 @@
|
||||
|
||||
使用结构体更新语法,我们可以通过更少的代码来达到相同的效果,如示例 5-7 所示。`..` 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/listing-05-07/src/main.rs:here}}
|
||||
```
|
||||
@ -76,7 +90,7 @@
|
||||
|
||||
示例 5-7 中的代码也在 `user2` 中创建了一个新实例,但该实例中 `email` 字段的值与 `user1` 不同,而 `username`、 `active` 和 `sign_in_count` 字段的值与 `user1` 相同。`..user1` 必须放在最后,以指定其余的字段应从 `user1` 的相应字段中获取其值,但我们可以选择以任何顺序为任意字段指定值,而不用考虑结构体定义中字段的顺序。
|
||||
|
||||
请注意,结构更新语法就像带有 `=` 的赋值,因为它移动了数据,就像我们在[“变量与数据交互的方式(一):移动”][move]部分讲到的一样。在这个例子中,我们在创建 `user2` 后不能再使用 `user1`,因为 `user1` 的 `username` 字段中的 `String` 被移到 `user2` 中。如果我们给 `user2` 的 `email` 和 `username` 都赋予新的 `String` 值,从而只使用 `user1` 的 `active` 和 `sign_in_count` 值,那么 `user1` 在创建 `user2` 后仍然有效。`active` 和 `sign_in_count` 的类型是实现 `Copy` trait 的类型,所以我们在[“变量与数据交互的方式(二):克隆”][copy] 部分讨论的行为同样适用。
|
||||
请注意,结构更新语法就像带有 `=` 的赋值,因为它移动了数据,就像我们在[“变量与数据交互的方式(一):移动”][move]部分讲到的一样。在这个例子中,总体上说我们在创建 `user2` 后不能就再使用 `user1` 了,因为 `user1` 的 `username` 字段中的 `String` 被移到 `user2` 中。如果我们给 `user2` 的 `email` 和 `username` 都赋予新的 `String` 值,从而只使用 `user1` 的 `active` 和 `sign_in_count` 值,那么 `user1` 在创建 `user2` 后仍然有效。`active` 和 `sign_in_count` 的类型是实现 `Copy` trait 的类型,所以我们在[“变量与数据交互的方式(二):克隆”][copy] 部分讨论的行为同样适用。
|
||||
|
||||
### 使用没有命名字段的元组结构体来创建不同的类型
|
||||
|
||||
@ -84,6 +98,8 @@
|
||||
|
||||
要定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color` 和 `Point` 元组结构体的定义和用法:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-01-tuple-structs/src/main.rs}}
|
||||
```
|
||||
@ -94,6 +110,8 @@
|
||||
|
||||
我们也可以定义一个没有任何字段的结构体!它们被称为 **类单元结构体**(*unit-like structs*)因为它们类似于 `()`,即[“元组类型”][tuples]一节中提到的 unit 类型。类单元结构体常常在你想要在某个类型上实现 trait 但不需要在类型中存储数据的时候发挥作用。我们将在第十章介绍 trait。下面是一个声明和实例化一个名为 `AlwaysEqual` 的 unit 结构的例子。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-04-unit-like-structs/src/main.rs}}
|
||||
```
|
||||
@ -118,9 +136,9 @@
|
||||
>
|
||||
> fn main() {
|
||||
> let user1 = User {
|
||||
> email: "someone@example.com",
|
||||
> username: "someusername123",
|
||||
> active: true,
|
||||
> username: "someusername123",
|
||||
> email: "someone@example.com",
|
||||
> sign_in_count: 1,
|
||||
> };
|
||||
> }
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-02-example-structs.md)
|
||||
> <br>
|
||||
> commit dd7e05275822d6cf790bcdae6983b3234141b5e7
|
||||
> commit 8612c4a5801b61f8d2e952f8bbbb444692ff1ec2
|
||||
|
||||
为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。
|
||||
|
||||
@ -60,7 +60,7 @@
|
||||
|
||||
<span class="caption">示例 5-10:定义 `Rectangle` 结构体</span>
|
||||
|
||||
这里我们定义了一个结构体并称其为 `Rectangle`。在大括号中定义了字段 `width` 和 `height`,类型都是 `u32`。接着在 `main` 中,我们创建了一个具体的 `Rectangle` 实例,它的宽是 30,高是 50。
|
||||
这里我们定义了一个结构体并称其为 `Rectangle`。在大括号中定义了字段 `width` 和 `height`,类型都是 `u32`。接着在 `main` 中,我们创建了一个具体的 `Rectangle` 实例,它的宽是 `30`,高是 `50`。
|
||||
|
||||
函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权,这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`。
|
||||
|
||||
@ -122,7 +122,7 @@ Rust **确实** 包含了打印出调试信息的功能,不过我们必须为
|
||||
{{#include ../listings/ch05-using-structs-to-structure-related-data/listing-05-12/output.txt}}
|
||||
```
|
||||
|
||||
好极了!这并不是最漂亮的输出,不过它显示这个实例的所有字段,毫无疑问这对调试有帮助。当我们有一个更大的结构体时,能有更易读一点的输出就好了,为此可以使用 `{:#?}` 替换 `println!` 字符串中的 `{:?}`。在这个例子中使用 `{:#?}` 风格将会输出:
|
||||
好极了!这并不是最漂亮的输出,不过它显示这个实例的所有字段,毫无疑问这对调试有帮助。当我们有一个更大的结构体时,能有更易读一点的输出就好了,为此可以使用 `{:#?}` 替换 `println!` 字符串中的 `{:?}`。在这个例子中使用 `{:#?}` 风格将会输出如下:
|
||||
|
||||
```console
|
||||
{{#include ../listings/ch05-using-structs-to-structure-related-data/output-only-02-pretty-debug/output.txt}}
|
||||
@ -144,7 +144,7 @@ Rust **确实** 包含了打印出调试信息的功能,不过我们必须为
|
||||
{{#include ../listings/ch05-using-structs-to-structure-related-data/no-listing-05-dbg-macro/output.txt}}
|
||||
```
|
||||
|
||||
我们可以看到第一点输出来自 *src/main.rs* 第 10 行,我们正在调试表达式 `30 * scale`,其结果值是60(为整数实现的 `Debug` 格式化是只打印它们的值)。在 *src/main.rs* 第 14行 的 `dbg!` 调用输出 `&rect1` 的值,即 `Rectangle` 结构。这个输出使用了更为易读的 `Debug` 格式。当你试图弄清楚你的代码在做什么时,`dbg!` 宏可能真的很有帮助!
|
||||
我们可以看到第一点输出来自 *src/main.rs* 第 10 行,我们正在调试表达式 `30 * scale`,其结果值是 `60`(为整数实现的 `Debug` 格式化是只打印它们的值)。在 *src/main.rs* 第 14行 的 `dbg!` 调用输出 `&rect1` 的值,即 `Rectangle` 结构。这个输出使用了更为易读的 `Debug` 格式。当你试图弄清楚你的代码在做什么时,`dbg!` 宏可能真的很有帮助!
|
||||
|
||||
除了 `Debug` trait,Rust 还为我们提供了很多可以通过 `derive` 属性来使用的 trait,他们可以为我们的自定义类型增加实用的行为。[附录 C][app-c] 中列出了这些 trait 和行为。第十章会介绍如何通过自定义行为来实现这些 trait,同时还有如何创建你自己的 trait。除了 `derive` 之外,还有很多属性;更多信息请参见 [Rust Reference][attributes] 的 Attributes 部分。
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch05-03-method-syntax.md)
|
||||
> <br>
|
||||
> commit dd7e05275822d6cf790bcdae6983b3234141b5e7
|
||||
> commit d339373a838fd312a8a9bcc9487e1ffbc9e1582f
|
||||
|
||||
**方法**(method)与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
|
||||
**方法**(method)与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在[第六章][enums]和[第十七章][trait-objects]讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
|
||||
|
||||
### 定义方法
|
||||
|
||||
@ -34,9 +34,9 @@
|
||||
{{#rustdoc_include ../listings/ch05-using-structs-to-structure-related-data/no-listing-06-method-field-interaction/src/main.rs:here}}
|
||||
```
|
||||
|
||||
在这里,我们选择让 `width` 方法在实例的 `width` 字段的值大于 0 时返回 `true`,等于 0 时则返回 `false`:我们可以出于任何目的,在同名的方法中使用同名的字段。在 `main` 中,当我们在 `rect1.width` 后面加上括号时。Rust 知道我们指的是方法 `width`。当我们不使用圆括号时,Rust 知道我们指的是字段 `width`。
|
||||
在这里,我们选择让 `width` 方法在实例的 `width` 字段的值大于 `0` 时返回 `true`,等于 `0` 时则返回 `false`:我们可以出于任何目的,在同名的方法中使用同名的字段。在 `main` 中,当我们在 `rect1.width` 后面加上括号时。Rust 知道我们指的是方法 `width`。当我们不使用圆括号时,Rust 知道我们指的是字段 `width`。
|
||||
|
||||
通常,但并不总是如此,与字段同名的方法将被定义为只返回字段中的值,而不做其他事情。这样的方法被称为 *getters*,Rust 并不像其他一些语言那样为结构字段自动实现它们。Getters 很有用,因为你可以把字段变成私有的,但方法是公共的,这样就可以把对字段的只读访问作为该类型公共 API 的一部分。我们将在第七章中讨论什么是公有和私有,以及如何将一个字段或方法指定为公有或私有。
|
||||
通常,但并不总是如此,与字段同名的方法将被定义为只返回字段中的值,而不做其他事情。这样的方法被称为 *getters*,Rust 并不像其他一些语言那样为结构字段自动实现它们。Getters 很有用,因为你可以把字段变成私有的,但方法是公共的,这样就可以把对字段的只读访问作为该类型公共 API 的一部分。我们将在[第七章][public]中讨论什么是公有和私有,以及如何将一个字段或方法指定为公有或私有。
|
||||
|
||||
> ### `->` 运算符到哪去了?
|
||||
>
|
||||
@ -114,7 +114,7 @@ Can rect1 hold rect3? false
|
||||
|
||||
关键字 `Self` 在函数的返回类型中代指在 `impl` 关键字后出现的类型,在这里是 `Rectangle`
|
||||
|
||||
使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个函数位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间。第七章会讲到模块。
|
||||
使用结构体名和 `::` 语法来调用这个关联函数:比如 `let sq = Rectangle::square(3);`。这个函数位于结构体的命名空间中:`::` 语法用于关联函数和模块创建的命名空间。[第七章][modules]会讲到模块。
|
||||
|
||||
### 多个 `impl` 块
|
||||
|
||||
@ -133,3 +133,8 @@ Can rect1 hold rect3? false
|
||||
结构体让你可以创建出在你的领域中有意义的自定义类型。通过结构体,我们可以将相关联的数据片段联系起来并命名它们,这样可以使得代码更加清晰。在 `impl` 块中,你可以定义与你的类型相关联的函数,而方法是一种相关联的函数,让你指定结构体的实例所具有的行为。
|
||||
|
||||
但结构体并不是创建自定义类型的唯一方法:让我们转向 Rust 的枚举功能,为你的工具箱再添一个工具。
|
||||
|
||||
[enums]: ch06-00-enums.html
|
||||
[trait-objects]: ch17-02-trait-objects.md
|
||||
[public]: ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html#exposing-paths-with-the-pub-keyword
|
||||
[modules]: ch07-02-defining-modules-to-control-scope-and-privacy.html
|
||||
|
Loading…
Reference in New Issue
Block a user