wip: 2024 edition

main
kazeno 20 hours ago
parent 9f90a61918
commit 5efbc9af1e

@ -393,7 +393,7 @@ You guessed: 5
let guess: u32 = guess.trim().parse().expect("Please type a number!");
```
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做 `guess` 的变量了吗?确实如此,不过 Rust 允许用一个新值来 **隐藏** _Shadowing_ `guess` 之前的值。这个功允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str``guess` 之类。[第三章][shadowing]会介绍 shadowing 的更多细节,现在只需知道这个功能经常用于将一个类型的值转换为另一个类型的值。
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做 `guess` 的变量了吗?确实如此,不过 Rust 允许用一个新值来 **遮蔽** _Shadowing_ `guess` 之前的值。这个功允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str``guess` 之类。[第三章][shadowing]会介绍 shadowing 的更多细节,现在只需知道这个功能经常用于将一个类型的值转换为另一个类型的值。
我们将这个新变量绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 指的是包含输入的字符串类型 `guess` 变量。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符,我们必须执行此方法才能将字符串与 `u32` 比较,因为 `u32` 只能包含数值型数据。用户必须输入 <kbd>enter</kbd> 键才能让 `read_line` 返回并输入他们的猜想这将会在字符串中增加一个换行newline符。例如用户输入 <kbd>5</kbd> 并按下 <kbd>enter</kbd>(在 Windows 上,按下 <kbd>enter</kbd> 键会得到一个回车符和一个换行符,`\r\n``guess` 看起来像这样:`5\n` 或者 `5\r\n`。`\n` 代表 “换行”,回车键;`\r` 代表 “回车”,回车键。`trim` 方法会消除 `\n` 或者 `\r\n`,结果只留下 `5`
@ -560,6 +560,6 @@ You win!
[doccargo]: http://doc.crates.io
[doccratesio]: http://doc.crates.io/crates-io.html
[match]: ch06-02-match.html
[shadowing]: ch03-01-variables-and-mutability.html#隐藏
[shadowing]: ch03-01-variables-and-mutability.html#遮蔽
[parse]: https://doc.rust-lang.org/std/primitive.str.html#method.parse
[integers]: ch03-02-data-types.html#整型

@ -1,8 +1,7 @@
# 常见编程概念
> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/main/src/ch03-00-common-programming-concepts.md)
> <br>
> commit d0acb2595c891de97a133d06635c50ab449dd65c
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-00-common-programming-concepts.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。很多编程语言的核心概念都是共通的,本章中展示的概念都不是 Rust 所特有的,不过我们会在 Rust 上下文中讨论它们,并解释使用这些概念的惯例。

@ -1,14 +1,13 @@
## 变量和可变性
> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch03-01-variables-and-mutability.md)
>
> commit 21a2ed14f4480dab62438dcc1130291bebc65379
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-01-variables-and-mutability.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
正如第二章中[“使用变量储存值”][storing-values-with-variables]<!-- ignore --> 部分提到的那样变量默认是不可改变的immutable。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择不使用不可变性
正如第二章中[“使用变量储存值”][storing-values-with-variables]<!-- ignore --> 部分提到的那样变量默认是不可改变的immutable。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择禁用它
当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码这些代码还不能编译我们会首次检查到不可变错误immutability error
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码这些代码还不能编译我们会首次检查到不可变错误immutability error
<span class="filename">文件名src/main.rs</span>
@ -22,7 +21,7 @@
{{#include ../listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/output.txt}}
```
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,但那只是表示程序不能安全的完成你想让它完成的工作;并 **不能** 说明你不是一个好程序员!经验丰富的 Rustacean 们一样会遇到编译错误。
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,但那只是表示程序不能安全的完成你想让它完成的工作;并**不能**说明你不是一个好程序员!经验丰富的 Rustacean 们一样会遇到编译错误。
错误信息指出错误的原因是 `不能对不可变变量 x 二次赋值```cannot assign twice to immutable variable `x` ``),因为你尝试对不可变变量 `x` 赋第二个值。
@ -40,7 +39,7 @@ Rust 编译器保证,如果声明一个值不会变,它就真的不会变,
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-02-adding-mut/src/main.rs}}
```
现在运行这个程序,出现如下内容:
现在运行这个程序,出现如下内容:
```console
{{#include ../listings/ch03-common-programming-concepts/no-listing-02-adding-mut/output.txt}}
@ -70,9 +69,9 @@ const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。
### 隐藏
### 遮蔽
正如在[第二章][comparing-the-guess-to-the-secret-number]猜数字游戏中所讲我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 **隐藏Shadowing** 了,这意味着当您使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量遮蔽了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被隐藏或第二个变量的作用域结束。可以用相同变量名称来隐藏一个变量,以及重复使用 `let` 关键字来多次隐藏,如下所示:
正如在[第二章][comparing-the-guess-to-the-secret-number]猜数字游戏中所讲我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 **遮蔽Shadowing** 了,这意味着当您使用变量的名称时,编译器将看到第二个变量。实际上,第二个变量遮蔽了第一个变量,此时任何使用该变量名的行为中都会视为是在使用第二个变量,直到第二个变量自己也被遮蔽或第二个变量的作用域结束。可以用相同变量名称来遮蔽一个变量,以及重复使用 `let` 关键字来多次遮蔽,如下所示:
<span class="filename">文件名src/main.rs</span>
@ -80,21 +79,21 @@ 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`。当该作用域结束时,内部 shadowing 的作用域也结束了,`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}}

@ -1,10 +1,9 @@
## 数据类型
> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/main/src/ch03-02-data-types.md)
> <br>
> commit d0acb2595c891de97a133d06635c50ab449dd65c
<!-- https://github.com/rust-lang/book/blob/main/src/ch03-02-data-types.md -->
<!-- commit a619cc5f073b1b59c026cf0f92ab061a46716325 -->
在 Rust 中,每一个值都属于某一个 **数据类型***data type*),这告诉 Rust 它被指定为何种数据以便明确数据处理方式。我们将看到两类数据类型子集标量scalar和复合compound
在 Rust 中,每一个值都有一个特定 **数据类型***data type*),这告诉 Rust 它被指定为何种数据以便明确数据处理方式。我们将看到两类数据类型子集标量scalar和复合compound
记住Rust 是 **静态类型***statically typed*)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,比如第二章的 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 使用 `parse``String` 转换为数字时,必须增加类型注解,像这样:
@ -26,7 +25,7 @@ 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>
@ -37,9 +36,9 @@ let guess: u32 = "42".parse().expect("Not a number!");
| 32-bit | `i32` | `u32` |
| 64-bit | `i64` | `u64` |
| 128-bit | `i128` | `u128` |
| arch | `isize` | `usize` |
| 架构相关 | `isize` | `usize` |
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式twos complement representation][twos-complement] 存储。
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[二进制补码形式twos 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。
@ -57,11 +56,11 @@ let guess: u32 = "42".parse().expect("Not a number!");
| Binary (二进制) | `0b1111_0000` |
| Byte (单字节字符)(仅限于`u8`) | `b'A'` |
那么该使用哪种类型的数字呢如果拿不定主意Rust 的默认类型通常是个不错的起点,数字类型默认是 `i32`。`isize` 或 `usize` 主要作为某些集合的索引。
那么该使用哪种类型的数字呢如果拿不定主意Rust 的默认类型通常是个不错的起点,型默认是 `i32`。`isize` 或 `usize` 主要作为某些集合的索引。
> ##### 整型溢出
>
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256`会发生什么呢?这被称为 “整型溢出”“integer overflow” ),这会导致以下两种行为之一的发生。当在 debug 模式编译时Rust 检查这类问题并使程序 *panic*这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256`就会发生 **整型溢出***integer overflow* ),这会导致以下两种行为之一的发生。当在 debug 模式编译时Rust 检查这类问题并使程序 *panic*。*panic* 这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
>
> 使用 `--release` flag 在 release 模式中构建时Rust **不会**检测会导致 panic 的整型溢出。相反发生整型溢出时Rust 会进行一种被称为二进制补码 wrapping*twos complement wrapping*)的操作。简而言之,比此类型能容纳最大值还大的值会回绕到最小值,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。程序不会 panic不过变量可能也不会是你所期望的值。依赖整型溢出 wrapping 的行为被认为是一种错误。
>
@ -75,19 +74,17 @@ let guess: u32 = "42".parse().expect("Not a number!");
Rust 也有两个原生的 **浮点数***floating-point numbers*类型它们是带小数点的数字。Rust 的浮点数类型是 `f32``f64`,分别占 32 位和 64 位。默认类型是 `f64`,因为在现代 CPU 中,它与 `f32` 速度几乎一样,不过精度更高。所有的浮点型都是有符号的。
这是一个展示浮点数的实例:
<span class="filename">文件名src/main.rs</span>
```rust
{{#rustdoc_include ../listings/ch03-common-programming-concepts/no-listing-06-floating-point/src/main.rs}}
```
浮点数采用 IEEE-754 标准表示。`f32` 是单精度浮点数,`f64` 是双精度浮点数。
浮点数采用 IEEE-754 标准表示。`f32` 是单精度浮点数,`f64` 是双精度浮点数。
#### 数值运算
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。下面的代码展示了如何在 `let` 语句中使用它们
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向零舍入到最接近的整数。下面的代码展示了如何在 `let` 语句中使用各种数值运算
<span class="filename">文件名src/main.rs</span>
@ -97,7 +94,7 @@ Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,然后绑定给一个变量。[附录 B][appendix_b]<!-- ignore --> 包含 Rust 提供的所有运算符的列表。
#### 布尔型
#### 布尔
正如其他大部分编程语言一样Rust 中的布尔类型有两个可能的值:`true` 和 `false`。Rust 中的布尔类型使用 `bool` 表示。例如:
@ -111,7 +108,7 @@ Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘
#### 字符类型
Rust 的 `char` 类型是语言中最原的字母类型。下面是一些声明 `char` 值的例子:
Rust 的 `char` 类型是语言中最原的字母类型。下面是一些声明 `char` 值的例子:
<span class="filename">文件名src/main.rs</span>
@ -119,7 +116,7 @@ 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` 类型的大小为四个字节 (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] 中将详细讨论这个主题。
### 复合类型
@ -127,7 +124,7 @@ Rust 的 `char` 类型是语言中最原生的字母类型。下面是一些声
#### 元组类型
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
元组是一个将多个不同类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
@ -147,7 +144,7 @@ Rust 的 `char` 类型是语言中最原生的字母类型。下面是一些声
程序首先创建了一个元组并绑定到 `tup` 变量上。接着使用了 `let` 和一个模式将 `tup` 分成了三个不同的变量,`x`、`y` 和 `z`。这叫做 **解构***destructuring*),因为它将一个元组拆成了三个部分。最后,程序打印出了 `y` 的值,也就是 `6.4`
我们也可以使用点号(`.`)后跟值的索引来直接访问它们。例如:
我们也可以使用点号(`.`)后跟值的索引来直接访问所需的元组元素。例如:
<span class="filename">文件名src/main.rs</span>
@ -163,7 +160,7 @@ Rust 的 `char` 类型是语言中最原生的字母类型。下面是一些声
另一个包含多个值的方式是 **数组***array*。与元组不同数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同Rust 中的数组长度是固定的。
我们将数组的值写成在方括号内,用逗号分隔:
我们将数组的值写成在方括号内,用逗号分隔的列表
<span class="filename">文件名src/main.rs</span>
@ -221,7 +218,8 @@ let a = [3; 5];
此代码编译成功。如果您使用 `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
thread 'main' panicked at src/main.rs:19:19:
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
```

Loading…
Cancel
Save