update to ch03-02

main
KaiserY 3 days ago
parent 8765f117a5
commit 7cf33de741

@ -400,7 +400,7 @@ let guess: u32 = guess.trim().parse().expect("Please type a number!");
另外,程序中的 `u32` 注解以及与 `secret_number` 的比较,意味着 Rust 会推断出 `secret_number` 也是 `u32` 类型。现在可以使用相同类型比较两个值了!
`parse` 方法只会在字符逻辑上确实可以转换为数字时才成功,因此很容易失败。例如,如果字符串里包含 `A👍%`,那就根本不可能把它转换成数字。正因如此,`parse` 方法会返回一个 `Result` 类型,就像前面在 [“使用 `Result` 类型来处理潜在的错误”](#handling-potential-failure-with-the-result-type) 中讨论过的 `read_line` 方法一样。这里我们再次使用 `expect` 方法来处理它。如果 `parse` 无法从字符串中生成数字,并返回 `Result``Err` 成员,`expect` 就会让游戏崩溃,并打印我们提供的消息。如果 `parse` 成功把字符串转换为数字,它就会返回 `Result``Ok` 成员,而 `expect` 会把 `Ok` 里保存的数字返回给我们。
`parse` 方法只会在字符逻辑上确实可以转换为数字时才成功,因此很容易失败。例如,如果字符串里包含 `A👍%`,那就根本不可能把它转换成数字。正因如此,`parse` 方法会返回一个 `Result` 类型,就像前面在 [“使用 `Result` 类型来处理潜在的错误”](#使用-result-类型来处理潜在的错误) 中讨论过的 `read_line` 方法一样。这里我们再次使用 `expect` 方法来处理它。如果 `parse` 无法从字符串中生成数字,并返回 `Result``Err` 成员,`expect` 就会让游戏崩溃,并打印我们提供的消息。如果 `parse` 成功把字符串转换为数字,它就会返回 `Result``Ok` 成员,而 `expect` 会把 `Ok` 里保存的数字返回给我们。
现在让我们运行程序!

@ -1,13 +1,12 @@
# 常见编程概念
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-00-common-programming-concepts.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/9cc190796f28505c7a9a9cacea42f50d895ff3bd/src/ch03-00-common-programming-concepts.md)
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。很多编程语言的核心概念都是共通的,本章中展示的概念都不是 Rust 所特有的,不过我们会在 Rust 上下文中讨论它们,并解释使用这些概念的惯例。
本章介绍一些几乎出现在所有编程语言中的概念,以及它们在 Rust 中是如何工作的。许多编程语言在其核心层面上都有很多共通之处。本章介绍的概念没有一个是 Rust 独有的,不过我们会在 Rust 的语境下讨论它们,并解释围绕这些概念的使用惯例。
具体来说,我们将会学习变量、基本类型、函数、注释和控制流。每一个 Rust 程序中都会用到这些基础知识,提早学习这些概念会让你在起步时就打下坚实的基础。
具体来说,你将学习变量、基本类型、函数、注释和控制流。这些基础内容会出现在每一个 Rust 程序中,尽早掌握它们会让你在起步时拥有一个坚实的基础。
> ## 关键字
> #### 关键字
> Rust 语言有一组保留的 **关键字***keywords*),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在[附录 A][appendix_a]<!-- ignore --> 中找到关键字的列表。
[appendix_a]: appendix-01-keywords.html

@ -1,13 +1,12 @@
## 变量和可变性
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-01-variables-and-mutability.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
[ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/9cc190796f28505c7a9a9cacea42f50d895ff3bd/src/ch03-01-variables-and-mutability.md)
正如第二章中[“使用变量储存值”][storing-values-with-variables]<!-- ignore --> 部分提到的那样变量默认是不可改变的immutable。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择禁用它
正如第二章中[“使用变量储存值”][storing-values-with-variables] 一节提到的那样变量默认是不可变的immutable。这是 Rust 给你的众多提醒之一,促使你以能充分利用 Rust 所提供的安全性和易于并发的方式来编写代码。不过,你仍然可以把变量设为可变。让我们来探讨 Rust 为何以及如何鼓励你偏向使用不可变性,以及为什么有时你会想要选择不这么做
当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码这些代码还不能编译我们会首次检查到不可变错误immutability error
接着,在新建的 *variables* 目录中,打开 *src/main.rs* 并将其中的代码替换为下面这段代码。它现在还不能编译
<span class="filename">文件名src/main.rs</span>
@ -21,15 +20,15 @@
{{#include ../listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/output.txt}}
```
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,但那只是表示程序不能安全的完成你想让它完成的工作;并**不能**说明你不是一个好程序员!经验丰富的 Rustacean 们一样会遇到编译错误。
这个例子展示了编译器如何帮助你发现程序中的错误。编译错误可能令人沮丧,但它们其实只意味着你的程序还没有以安全的方式完成你希望它完成的工作;这**并不**意味着你不是一个好程序员!经验丰富的 Rustaceans 也一样会遇到编译错误。
错误信息指出错误的原因是 `不能对不可变变量 x 二次赋值```cannot assign twice to immutable variable `x` ``),因为你尝试对不可变变量 `x` 赋第二个值。
你收到的错误信息 ``cannot assign twice to immutable variable `x` ``,是因为你试图给不可变变量 `x` 赋第二个值。
在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变而另一部分代码改变了这个值第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 **有时** 会改变值
当我们尝试修改一个被指定为不可变的值时,能够得到编译时错误是很重要的,因为这种情况可能会导致 bug。如果代码的一部分假设某个值永远不会改变而另一部分代码却改变了这个值那么前一部分代码就可能无法按设计那样运行。事后要追踪这类 bug 的根源会非常困难,尤其是当第二段代码只是**有时**才会修改这个值的时候
Rust 编译器保证,如果声明一个值不会变,它就真的不会变,所以你不必自己跟踪它。这意味着你的代码更易于推导。
不过可变性也是非常有用的,可以用来更方便地编写代码。尽管变量默认是不可变的,你仍然可以在变量名前添加 `mut` 来使其可变,正如在[第二章][storing-values-with-variables]所做的那样。`mut` 也向读者表明了其他代码将会改变这个变量值的意图
不过,可变性也非常有用,能让代码写起来更方便。尽管变量默认是不可变的,你仍然可以像[第二章][storing-values-with-variables]中那样,在变量名前加上 `mut` 使其变为可变。添加 `mut` 也能向未来阅读代码的人传达一种意图:这个变量的值将会被代码的其他部分改变
例如,让我们将 *src/main.rs* 修改为如下代码:
@ -45,17 +44,17 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
{{#include ../listings/ch03-common-programming-concepts/no-listing-02-adding-mut/output.txt}}
```
通过 `mut`,允许把绑定到 `x` 的值从 `5` 改成 `6`。是否让变量可变的最终决定权仍然在你,取决于在某个特定情况下,你是否认为变量可变会让代码更加清晰明了
使用 `mut` 后,我们就可以把绑定到 `x` 的值从 `5` 改成 `6`。归根结底,是否使用可变性由你自己决定,这取决于在特定场景下你觉得怎样会让代码更清晰
### 常量
类似于不可变变量,*常量 (constants)* 是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
和不可变变量类似,*常量constants* 也是绑定到某个名称且不允许改变的值,不过常量和变量之间还是有一些区别。
首先,不允许对常量使用 `mut`。常量不光默认不可变,它总是不可变。声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,[“数据类型”][data-types] 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可
首先,你不能对常量使用 `mut`。常量不只是默认不可变,它们永远都是不可变的。声明常量时要用 `const` 关键字,而不是 `let`,并且 *必须* 标注值的类型。在下一节[“数据类型”][data-types]中,我们会介绍类型和类型注解,所以现在先不用担心细节;你只要记住:声明常量时必须总是标注类型
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
最后一个区别是,常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。
最后一个区别是,常量只能被设置为常量表达式,而不能是那些只能在运行时计算出来的值。
下面是一个声明常量的例子:
@ -63,15 +62,15 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
```
常量的名称是 `THREE_HOURS_IN_SECONDS`,它的值被设置为 60一分钟内的秒数乘以 60一小时内的分钟数再乘以 3我们在这个程序中要计算的小时数的结果。Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是将此常量设置为值 10,800。有关声明常量时可以使用哪些操作的详细信息请参阅 [Rust Reference 的常量求值部分][const-eval]。
这个常量的名字是 `THREE_HOURS_IN_SECONDS`,它的值通过 60一分钟中的秒数乘以 60一小时中的分钟数再乘以 3我们这个程序中要计算的小时数得出。Rust 对常量的命名约定是全部大写,并用下划线分隔单词。编译器可以在编译时对一组有限的操作进行求值,因此我们可以选择用更易于理解和验证的方式来写出这个值,而不是将此常量设置为值 10,800。关于声明常量时可以使用哪些操作的更多信息请参阅 [Rust Reference 中关于常量求值的部分][const-eval]。
声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速
其声明所在的作用域内,常量在程序运行的整个过程中都有效。这一特性使常量非常适合作为应用领域中的全局值,比如游戏中所有玩家能够获得的最高分,或者光速这样的值
将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值
把散落在应用程序中的硬编码值提取为常量,有助于让以后维护代码的人理解这个值的含义。如果未来需要更新这个硬编码值,也只需要修改一个地方
### 遮蔽
正如在[第二章][comparing-the-guess-to-the-secret-number]猜数字游戏中所讲我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 **遮蔽Shadowing** 了,这意味着当您使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量遮蔽了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被遮蔽或第二个变量的作用域结束。可以用相同变量名称来遮蔽一个变量,以及重复使用 `let` 关键字来多次遮蔽,如下所示:
正如我们在[第二章][comparing-the-guess-to-the-secret-number]的猜数字游戏中看到的我们可以定义一个与之前变量同名的新变量。Rustaceans 把这种情况称为第一个变量被第二个变量 **遮蔽shadowed** 了。这意味着,当你使用这个变量名时,编译器看到的是第二个变量。实际上,第二个变量会遮住第一个变量,使得后续所有对该名称的使用都指向第二个变量,直到它自己又被遮蔽,或者它的作用域结束。我们可以通过重复使用同一个变量名并再次写出 `let` 关键字来遮蔽一个变量,如下所示:
<span class="filename">文件名src/main.rs</span>
@ -79,27 +78,27 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-03-shadowing/src/main.rs}}
```
这个程序首先`x` 绑定到值 `5` 上。接着通过 `let x =` 创建了一个新变量 `x`,获取初始值并加 `1`,这样 `x` 的值就变成 `6` 了。然后,在使用花括号创建的内部作用域内,第三个 `let` 语句也遮蔽了 `x` 并创建了一个新的变量,将之前的值乘以 `2``x` 得到的值是 `12`。当该作用域结束时,内部遮蔽的作用域也结束了,`x` 又返回到 `6`。运行这个程序,它会有如下输出:
这个程序首先`x` 绑定到值 `5`。接着,它再次写出 `let x =`,创建了一个新的变量 `x`,取原来的值并加上 `1`,于是 `x` 的值变成了 `6`。然后,在由花括号创建的内部作用域中,第三个 `let` 语句再次遮蔽了 `x`,并创建了一个新变量,把之前的值乘以 `2`,因此 `x` 的值变成了 `12`。当这个作用域结束时,内部的遮蔽也随之结束,`x` 又回到 `6`。运行这个程序时,会得到如下输出:
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-03-shadowing/output.txt}}
```
遮蔽与将变量标记为 `mut` 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 `let` 关键字,就会导致编译时错误。通过使用 `let`,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的
遮蔽和把变量标记为 `mut` 是不同的。如果你不小心尝试在没有使用 `let` 关键字的情况下重新给变量赋值,就会得到编译时错误。而通过再次使用 `let`,我们可以对这个值做一些变换,同时又能让变量在变换完成后继续保持不可变
`mut` 与遮蔽的另一个区别是,当再次使用 `let` 时,实际上创建了一个新变量,我们可以改变值的类型,并且复用这个名字。例如,假设程序请求用户输入空格字符来说明希望在文本之间显示多少个空格,接下来我们想将输入存储成数字(多少个空格)
`mut` 和遮蔽之间的另一个区别是:当我们再次使用 `let` 时,实际上是在创建一个新变量,因此我们可以改变值的类型,同时继续复用相同的名字。例如,假设程序要求用户输入若干空格,以表示他们希望在某段文本之间显示多少空格,随后我们想把这个输入保存成一个数字
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-04-shadowing-can-change-types/src/main.rs:here}}
```
第一个 `spaces` 变量是字符串类型,第二个 `spaces` 变量是数字类型。遮蔽使我们不必使用不同的名字,如 `spaces_str``spaces_num`;相反,我们可以复用 `spaces` 这个更简单的名字。然而,如果尝试使用 `mut`,将会得到一个编译时错误,如下所示
第一个 `spaces` 变量是字符串类型,第二个 `spaces` 变量是数字类型。遮蔽让我们不必想出不同的名字,比如 `spaces_str``spaces_num`;相反,我们可以继续复用更简单的 `spaces`。不过,如果像下面这样尝试使用 `mut`,就会得到一个编译时错误
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-05-mut-cant-change-types/src/main.rs:here}}
```
这个错误说明,我们不能改变量的类型:
这个错误说明,我们不能改变量的类型:
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-05-mut-cant-change-types/output.txt}}

@ -1,17 +1,16 @@
## 数据类型
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-02-data-types.md -->
<!-- commit a619cc5f073b1b59c026cf0f92ab061a46716325 -->
[ch03-02-data-types.md](https://github.com/rust-lang/book/blob/9cc190796f28505c7a9a9cacea42f50d895ff3bd/src/ch03-02-data-types.md)
在 Rust 中,每一个值都有一个特定 **数据类型***data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集标量scalar和复合compound
在 Rust 中,每个值都属于某种特定的 **数据类型***data type*),这会告诉 Rust 当前指定的是什么种类的数据,从而知道该如何处理这些数据。我们将看到两类数据类型的子集标量scalar和复合compound
记住Rust 是 **静态类型***statically typed*)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,比如第二章的 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 使用 `parse``String` 转换为数字时,必须增加类型注解,像这样:
记住Rust 是 **静态类型***statically typed*)语言,也就是说,它必须在编译时就知道所有变量的类型。编译器通常可以根据值以及它的使用方式推断出我们想要使用的类型。但在存在多种可能类型的情况下,比如我们在第二章[“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number]一节中使用 `parse``String` 转换为数值类型时,就必须加上类型注解,像这样:
```rust
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}}
@ -25,48 +24,48 @@ let guess: u32 = "42".parse().expect("Not a number!");
#### 整型
**整型** 是一个没有小数部分的数字。我们在第二章使用过 `u32` 整数类型。该类型声明表明,它关联的值应该是一个占据 32 比特位的无符号整数(有符号整数类型以 `i` 开头而不是 `u`)。表格 3-1 展示了 Rust 内建的整数类型。我们可以使用其中的任一个来声明一个整数值的类型。
**整型** 是没有小数部分的数字。我们在第二章已经用过 `u32` 这种整数类型。这个类型声明表明,它关联的值应该是一个占用 32 位空间的无符号整数(有符号整数类型以 `i` 开头而不是 `u`)。表格 3-1 展示了 Rust 内建的整数类型。我们可以使用其中任意一种来声明整数值的类型。
<span class="caption">表格 3-1: Rust 中的整型</span>
| 长度 | 有符号 | 无符号 |
|---------|---------|----------|
| 8-bit | `i8` | `u8` |
| 16-bit | `i16` | `u16` |
| 32-bit | `i32` | `u32` |
| 64-bit | `i64` | `u64` |
| 128-bit | `i128` | `u128` |
| 架构相关 | `isize` | `usize` |
| 长度 | 有符号 | 无符号 |
|------|--------|--------|
| 8-bit | `i8` | `u8` |
| 16-bit | `i16` | `u16` |
| 32-bit | `i32` | `u32` |
| 64-bit | `i64` | `u64` |
| 128-bit | `i128` | `u128` |
| 架构相关 | `isize` | `usize` |
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[二进制补码形式twos complement representation][twos-complement] 存储
每一种变体都可以是有符号或无符号的,并且具有明确的大小。**有符号** 和 **无符号** 指的是数字是否可能为负数。换句话说,这个数字是需要带符号的(有符号),还是它永远为正,因此无需符号(无符号)。这有点像我们在纸上写数字:当符号有意义时,数字前面会带上加号或减号;但如果可以安全地假定它是正数,通常就不会写加号。有符号数使用[二进制补码twos complement][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。
另外,`isize` 和 `usize` 类型依赖运行程序的计算机架构64 位架构上它们是 64 位的32 位架构上它们是 32 位的。
可以使用表格 3-2 中的任何一种形式编写数字字面值。请注意可以是多种数字类型的数字字面值允许使用类型后缀,例如 `57u8` 来指定类型,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`,它的值与你指定的 `1000` 相同。
你可以使用表格 3-2 中展示的任意一种形式来编写整数字面值。请注意,那些可能对应多种数值类型的数字字面值可以带上类型后缀,例如 `57u8`,用来显式指定类型。数字字面值也可以使用 `_` 作为视觉分隔符,方便阅读,例如 `1_000`,它和 `1000` 的值完全相同。
<span class="caption">表格 3-2: Rust 中的整型字面值</span>
| 数字字面值 | 例子 |
|------------------|---------------|
| Decimal (十进制) | `98_222` |
| Hex (十六进制) | `0xff` |
| Octal (八进制) | `0o77` |
| Binary (二进制) | `0b1111_0000` |
| Byte (单字节字符)(仅限于`u8`) | `b'A'` |
| 数字字面值 | 例子 |
|------------|------|
| Decimal(十进制) | `98_222` |
| Hex(十六进制) | `0xff` |
| Octal(八进制) | `0o77` |
| Binary(二进制) | `0b1111_0000` |
| Byte(字节字面值,仅限 `u8` | `b'A'` |
那么该使用哪种类型的数字呢如果拿不定主意Rust 的默认类型通常是个不错的起点,整型默认是 `i32`。`isize` 或 `usize` 主要作为某些集合的索引
那么该使用哪种整型呢如果拿不定主意Rust 的默认类型通常是一个不错的起点:整型默认是 `i32`。而 `isize``usize` 主要用在对某种集合进行索引的场景中
> ##### 整型溢出
>
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时就会发生 **整型溢出***integer overflow* ),这会导致以下两种行为之一的发生。当在 debug 模式编译时Rust 检查这类问题并使程序 *panic*。*panic* 这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
> 假设你有一个 `u8` 类型的变量,它可以保存 `0``255` 之间的值。如果你试图把它改成超出该范围的值,比如 `256`,就会发生 **整型溢出***integer overflow*),并可能导致两种行为之一。当你在 debug 模式下编译时Rust 会加入整型溢出的检查,并在发生这种情况时让程序在运行时 *panic*。Rust 用 *panicking* 这个术语表示程序因错误而退出;我们会在第九章[“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic]一节中更深入地讨论 panic。
>
> 使用 `--release` flag 在 release 模式中构建时Rust **不会**检测会导致 panic 的整型溢出。相反发生整型溢出时Rust 会进行一种被称为二进制补码 wrapping*twos complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。程序不会 panic不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。
> 当你使用 `--release` flag 在 release 模式下编译时Rust **不会**加入会导致 panic 的整型溢出检查。相反如果发生溢出Rust 会执行一种叫做 *twos complement wrapping* 的行为。简而言之,超过该类型最大值的数会“回绕”到该类型所能表示的最小值。对于 `u8` 来说,`256` 会变成 `0``257` 会变成 `1`,依此类推。程序不会 panic但变量得到的值很可能不是你原本期望的值。依赖整型溢出的回绕行为通常被认为是一种错误。
>
> 为了显式地处理溢出的可能性,可以使用这几类标准库提供的原始数字类型方法:
> * 所有模式下都可以使用 `wrapping_*` 方法进行 wrapping如 `wrapping_add`
> * 如果 `checked_*` 方法出现溢出,则返回 `None`
> * 如果 `checked_*` 方法发生溢出,则返回 `None`
> * 用 `overflowing_*` 方法返回值和一个布尔值,表示是否出现溢出
> * 用 `saturating_*` 方法在值的最小值或最大值处进行饱和处理
@ -80,7 +79,7 @@ Rust 也有两个原生的 **浮点数***floating-point numbers*)类型,
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-06-floating-point/src/main.rs}}
```
浮点数采用 IEEE-754 标准表示。(`f32` 是单精度浮点数,`f64` 是双精度浮点数。)
浮点数按照 IEEE-754 标准表示。
#### 数值运算
@ -104,7 +103,7 @@ Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-08-boolean/src/main.rs}}
```
使用布尔值的主要场景是条件表达式,例如 `if` 表达式。在 [“控制流”“Control Flow”][control-flow] 部分将介绍 `if` 表达式在 Rust 中如何工作
布尔值最主要的使用场景是条件表达式,例如 `if` 表达式。我们会在[“控制流”][control-flow]一节介绍 `if` 表达式在 Rust 中是如何工作的
#### 字符类型
@ -116,17 +115,17 @@ Rust 的 `char` 类型是语言中最原始的字母类型。下面是一些声
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-09-char/src/main.rs}}
```
注意,我们用单引号声明 `char` 字面值而与之相反的是使用双引号声明字符串字面值。Rust 的 `char` 类型的大小为四个字节 (four bytes),并代表了一个 Unicode 标量值Unicode Scalar Value这意味着它可以比 ASCII 表示更多内容。在 Rust 中带变音符号的字母Accented letters中文、日文、韩文等字符emoji绘文字以及零长度的空白字符都是有效的 `char` 值。Unicode 标量值包含从 `U+0000``U+D7FF``U+E000``U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 [“使用字符串储存 UTF-8 编码的文本”][strings] 中将详细讨论这个主题。
注意,我们使用单引号来表示 `char` 字面值而字符串字面值使用的是双引号。Rust 的 `char` 类型大小为 4 个字节,并表示一个 Unicode 标量值Unicode Scalar Value这意味着它所能表示的内容远不止 ASCII。带重音符号的字母中文、日文、韩文字符emoji以及零宽空格都是 Rust 中合法的 `char` 值。Unicode 标量值的范围包括 `U+0000``U+D7FF`,以及 `U+E000``U+10FFFF`。不过,“字符”并不是 Unicode 中一个严格对应的概念,因此你直觉上认为的“字符”未必和 Rust 中的 `char` 一一对应。我们会在第八章[“使用字符串储存 UTF-8 编码的文本”][strings]中更详细地讨论这个主题。
### 复合类型
**复合类型***Compound types*可以将多个值组合成一个类型。Rust 有两个原生的复合类型元组tuple和数组array
**复合类型***compound types*可以把多个值组合成一个类型。Rust 有两种原生的复合类型元组tuple和数组array
#### 元组类型
元组是一个将多个不同类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
元组是一种将多个不同类型的值组合成一个复合类型的通用方式。元组长度固定:一旦声明,它的大小就不能增长或缩小。
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
我们通过在圆括号中写一组由逗号分隔的值来创建元组。元组中的每个位置都有一个类型,而且这些不同位置上的值类型不必相同。下面这个例子中加入了可选的类型注解:
<span class="filename">文件名src/main.rs</span>
@ -134,7 +133,7 @@ Rust 的 `char` 类型是语言中最原始的字母类型。下面是一些声
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-10-tuples/src/main.rs}}
```
`tup` 变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配pattern matching来解构destructure元组,像这样:
变量 `tup` 绑定到整个元组上,因为元组本身会被视为一个单独的复合值。为了从元组中取出单个值,我们可以使用模式匹配pattern matching来解构destructure元组像这样
<span class="filename">文件名src/main.rs</span>
@ -154,11 +153,11 @@ Rust 的 `char` 类型是语言中最原始的字母类型。下面是一些声
这个程序创建了一个元组,`x`,然后使用其各自的索引访问元组中的每个元素。跟大多数编程语言一样,元组的第一个索引值是 0。
不带任何值的元组有个特殊的名称,叫做 **单元unit** 元组。这种值以及对应的类型都写作 `()`,表示空值或空的返回类型。如果表达式不返回任何其他值,则会隐式返回单元值。
不带任何值的元组有一个特殊名字,叫做 **单元unit**。这种值以及其对应的类型都写作 `()`,表示空值或空的返回类型。如果一个表达式没有返回任何其他值,它就会隐式返回单元值。
#### 数组类型
另一个包含多个值的方式是 **数组***array*。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同Rust 中的数组长度是固定的。
另一种包含多个值的方式是 **数组***array*。和元组不同数组中的每个元素都必须具有相同类型。Rust 中的数组也不同于某些其他语言中的数组Rust 的数组长度是固定的。
我们将数组的值写成在方括号内,用逗号分隔的列表:
@ -168,9 +167,9 @@ Rust 的 `char` 类型是语言中最原始的字母类型。下面是一些声
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-13-arrays/src/main.rs}}
```
当你想要在栈stack而不是在堆heap上为数据分配空间[第四章][stack-and-heap]将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,那么很可能应该使用 vector。[第八章][vectors]会详细讨论 vector。
当你希望把数据分配在栈stack上而不是堆heap上时[第四章][stack-and-heap]会更详细地讨论栈与堆),或者当你想确保始终拥有固定数量的元素时,数组就非常有用。不过,数组不如 vector 类型灵活。vector 是标准库提供的一种类似数组的集合类型,它 **允许** 长度增长或缩小。如果你不确定该用数组还是 vector那么很可能你应该用 vector。[第八章][vectors]会更详细地讨论 vector。
然而,当你确定元素个数不会改变时,数组会更有用。例如,当你在一个程序中使用月份名字时,你更应趋向于使用数组而不是 vector因为你确定只会有 12 个元素。
不过,当你明确知道元素个数不会变化时,数组就更有用。例如,如果你在程序中使用月份名称,那么你大概会选择数组而不是 vector因为你知道它始终只有 12 个元素。
```rust
let months = ["January", "February", "March", "April", "May", "June", "July",
@ -195,7 +194,7 @@ let a = [3; 5];
##### 访问数组元素
数组是可以在栈 (stack) 上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素,像这样
数组是在栈stack上分配的一整块、大小已知且固定的内存。你可以像下面这样使用索引来访问数组中的元素
<span class="filename">文件名src/main.rs</span>
@ -207,7 +206,7 @@ let a = [3; 5];
##### 无效的数组元素访问
让我们看看如果我们访问数组结尾之后的元素会发生什么呢?比如你执行以下代码,它使用类似于第 2 章中的猜数字游戏的代码从用户那里获取数组索引:
让我们看看如果尝试访问数组末尾之后的元素会发生什么。假设你运行下面这段代码,它类似于第 2 章中的猜数字游戏:从用户那里读取一个数组索引。
<span class="filename">文件名src/main.rs</span>
@ -215,7 +214,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 src/main.rs:19:19:
@ -223,9 +222,9 @@ index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
程序在索引操作中使用一个无效的值时导致 **运行时** 错误。程序带着错误信息退出,并且没有执行最后的 `println!` 语句。当尝试用索引访问一个元素时Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度Rust 会 *panic*,这是 Rust 术语,它用于程序因为错误而退出的情况。这种检查必须在运行时进行,特别是在这种情况下,因为编译器不可能知道用户在以后运行代码时将输入什么值。
程序在索引操作中使用了无效值,因此产生了一个 **运行时** 错误。程序带着错误信息退出,并且没有执行最后那条 `println!` 语句。当你尝试通过索引访问元素时Rust 会检查你指定的索引是否小于数组长度。如果索引大于或等于数组长度Rust 就会 *panic*。这种检查必须在运行时完成,尤其是在这种场景下,因为编译器不可能知道用户之后运行代码时会输入什么值。
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中并没有进行这类检查这样当提供了一个不正确的索引时就会访问无效的内存。通过立即退出而不是允许内存访问并继续执行Rust 让你避开此类错误。第九章会更详细地讨论 Rust 的错误处理机制,以及如何编写可读性强而又安全的代码,使程序既不会 panic 也不会导致非法内存访问。
这是 Rust 内存安全原则在实践中的一个例子。在许多底层语言中不会进行这种检查因此如果你提供了错误的索引就可能访问到无效内存。Rust 通过立即退出,而不是允许这次内存访问继续发生并让程序往下执行,来保护你免受这类错误的影响。第九章会更详细地讨论 Rust 的错误处理机制,以及如何编写既可读又安全的代码,让程序既不会 panic也不会发生非法内存访问。
[comparing-the-guess-to-the-secret-number]:
ch02-00-guessing-game-tutorial.html#比较猜测的数字和秘密数字
@ -235,5 +234,4 @@ ch02-00-guessing-game-tutorial.html#比较猜测的数字和秘密数字
[stack-and-heap]: ch04-01-what-is-ownership.html#栈stack与堆heap
[vectors]: ch08-01-vectors.html
[unrecoverable-errors-with-panic]: ch09-01-unrecoverable-errors-with-panic.html
[wrapping]: https://doc.rust-lang.org/std/num/struct.Wrapping.html
[appendix_b]: appendix-02-operators.html

Loading…
Cancel
Save