mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-14 04:41:49 +08:00
docs: update the Chapter 3 and improve some of the wording
This commit is contained in:
parent
f5a05c153b
commit
7e368ea706
@ -476,7 +476,7 @@ error: aborting due to previous error
|
||||
Could not compile `guessing_game`.
|
||||
```
|
||||
|
||||
错误的核心表明这里有 **不匹配的类型**(*mismatched types*)。Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 `let guess = String::new()` 时,Rust 推断出 `guess` 应该是 `String` 类型,并不需要我们写出类型。另一方面,`secret_number`,是数字类型。几个数字类型拥有 1 到 100 之间的值:32 位数字 `i32`;32 位无符号数字 `u32`;64 位数字 `i64` 等等。Rust 默认使用 `i32`,所以它是 `secret_number` 的类型,除非增加类型信息,或任何能让 Rust 推断出不同数值类型的信息。这里错误的原因在于 Rust 不会比较字符串类型和数字类型。
|
||||
错误的核心表明这里有 **不匹配的类型**(*mismatched types*)。Rust 有一个静态强类型系统,同时也有类型推断。当我们写出 `let guess = String::new()` 时,Rust 推断出 `guess` 应该是 `String` 类型,并不需要我们写出类型。另一方面,`secret_number`,是数字类型。有几个数字类型拥有 1 到 100 之间的值:32 位数字 `i32`;32 位无符号数字 `u32`;64 位数字 `i64` 等等。Rust 默认使用 `i32`,所以它是 `secret_number` 的类型,除非增加类型信息,或任何能让 Rust 推断出不同数值类型的信息。这里错误的原因在于 Rust 不会比较字符串类型和数字类型。
|
||||
|
||||
所以我们必须把从输入中读取到的 `String` 转换为一个真正的数字类型,才好与秘密数字进行比较。这可以通过在 `main` 函数体中增加另一行代码来实现:
|
||||
|
||||
@ -565,7 +565,7 @@ Too big!
|
||||
|
||||
如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保 loop 循环中的代码多缩进四个空格,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像无法退出啊!
|
||||
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 [“比较猜测与秘密数字”](#comparing-the-guess-to-the-secret-number) 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 [“比较猜测与秘密数字”](#比较猜测的数字和秘密数字) 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
|
||||
```console
|
||||
$ cargo run
|
||||
@ -722,4 +722,4 @@ fn main() {
|
||||
本项目通过动手实践,向你介绍了 Rust 新概念:`let`、`match`、方法、函数、使用外部 crate 等等,接下来的几章,你会继续深入学习这些概念。第三章介绍大部分编程语言都有的概念,比如变量、数据类型和函数,以及如何在 Rust 中使用它们。第四章探索所有权(ownership),这是一个 Rust 同其他语言大不相同的功能。第五章讨论结构体和方法的语法,而第六章侧重解释枚举。
|
||||
|
||||
[variables-and-mutability]:
|
||||
ch03-01-variables-and-mutability.html#variables-and-mutability
|
||||
ch03-01-variables-and-mutability.html#变量和可变性
|
||||
|
@ -2,11 +2,13 @@
|
||||
|
||||
> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/main/src/ch03-00-common-programming-concepts.md)
|
||||
> <br>
|
||||
> commit 1f49356cb21cbc27bc5359bfe655d26757d4b137
|
||||
> commit 05d9c4c2312a6542f792492d17a62f79ad6dfd7b
|
||||
|
||||
本章介绍一些几乎所有编程语言都有的概念,以及它们在 Rust 中是如何工作的。很多编程语言的核心概念都是共通的,本章中展示的概念都不是 Rust 所特有的,不过我们会在 Rust 上下文中讨论它们,并解释使用这些概念的惯例。
|
||||
|
||||
具体来说,我们将会学习变量、基本类型、函数、注释和控制流。每一个 Rust 程序中都会用到这些基础知识,提早学习这些概念会让你在起步时就打下坚实的基础。
|
||||
|
||||
> ## 关键字
|
||||
> Rust 语言有一组保留的 **关键字**(*keywords*),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在附录 A 中找到关键字的列表。
|
||||
> Rust 语言有一组保留的 **关键字**(*keywords*),就像大部分语言一样,它们只能由语言本身使用。记住,你不能使用这些关键字作为变量或函数的名称。大部分关键字有特殊的意义,你将在 Rust 程序中使用它们完成各种任务;一些关键字目前没有相应的功能,是为将来可能添加的功能保留的。可以在[附录 A][appendix_a]<!-- ignore --> 中找到关键字的列表。
|
||||
|
||||
[appendix_a]: appendix-01-keywords.md
|
@ -2,9 +2,9 @@
|
||||
|
||||
> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch03-01-variables-and-mutability.md)
|
||||
> <br>
|
||||
> commit d69b1058c660abfe1d274c58d39c06ebd5c96c47
|
||||
> commit 05d9c4c2312a6542f792492d17a62f79ad6dfd7b
|
||||
|
||||
第二章中提到过,变量默认是不可改变的(immutable)。这是推动你以充分利用 Rust 提供的安全性和简单并发性来编写代码的众多方式之一。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择不使用不可变性。
|
||||
正如第二章中[“使用变量储存值”][storing-values-with-variables]<!-- ignore --> 部分提到的那样,变量默认是不可改变的(immutable)。这是 Rust 提供给你的众多优势之一,让你得以充分利用 Rust 提供的安全性和简单并发性来编写代码。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 为何及如何鼓励你利用不可变性,以及何时你会选择不使用不可变性。
|
||||
|
||||
当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
|
||||
|
||||
@ -23,20 +23,32 @@ fn main() {
|
||||
|
||||
保存并使用 `cargo run` 运行程序。应该会看到一条错误信息,如下输出所示:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (file:///projects/variables)
|
||||
error[E0384]: cannot assign twice to immutable variable `x`
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
2 | let x = 5;
|
||||
| - first assignment to `x`
|
||||
| -
|
||||
| |
|
||||
| first assignment to `x`
|
||||
| help: consider making this binding mutable: `mut x`
|
||||
3 | println!("The value of x is: {}", x);
|
||||
4 | x = 6;
|
||||
| ^^^^^ cannot assign twice to immutable variable
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0384`.
|
||||
error: could not compile `variables`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,但那只是表示程序不能安全的完成你想让它完成的工作;并 **不能** 说明你不是一个好程序员!经验丰富的 Rustacean 们一样会遇到编译错误。
|
||||
|
||||
错误信息指出错误的原因是 `不能对不可变变量 x 二次赋值`(`cannot assign twice to immutable variable x`),因为你尝试对不可变变量 `x` 赋第二个值。
|
||||
错误信息指出错误的原因是 `不能对不可变变量 x 二次赋值`(``cannot assign twice to immutable variable `x` ``),因为你尝试对不可变变量 `x` 赋第二个值。
|
||||
|
||||
在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 **有时** 会改变值。
|
||||
|
||||
@ -59,10 +71,10 @@ fn main() {
|
||||
|
||||
现在运行这个程序,出现如下内容:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (file:///projects/variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
|
||||
Running `target/debug/variables`
|
||||
The value of x is: 5
|
||||
The value of x is: 6
|
||||
@ -82,21 +94,23 @@ The value of x is: 6
|
||||
|
||||
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
|
||||
|
||||
最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。
|
||||
最后一个区别是,常量只能被设置为常量表达式,而不可以是其他任何只能在运行时计算出的值。
|
||||
|
||||
这是一个声明常量的例子,它的名称是 `MAX_POINTS`,值是 100,000。(Rust 常量的命名规范是使用下划线分隔的大写字母单词,并且可以在数字字面值中插入下划线来提升可读性):
|
||||
下面是一个声明常量的例子,常量的名称是 `HREE_HOURS_IN_SECONDS`,它的值被设置为 60(一分钟内的秒数)乘以 60(一小时内的分钟数)再乘以 3(我们在这个程序中要计算的小时数)的结果:
|
||||
|
||||
```rust
|
||||
const MAX_POINTS: u32 = 100_000;
|
||||
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
|
||||
```
|
||||
|
||||
在声明它的作用域之中,常量在整个程序生命周期中都有效,这使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
|
||||
Rust 对常量的命名约定是在单词之间使用全大写加下划线。编译器能够在编译时计算一组有限的操作,这使我们可以选择以更容易理解和验证的方式写出此值,而不是将此常量设置为值10,800。有关声明常量时可以使用哪些操作的详细信息,请参阅 [Rust Reference 的常量求值部分][const-eval]。
|
||||
|
||||
在声明它的作用域之中,常量在整个程序生命周期中都有效,此属性使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
|
||||
|
||||
将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。
|
||||
|
||||
### 隐藏(Shadowing)
|
||||
|
||||
正如在第二章猜猜看游戏的 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 中所讲,我们可以定义一个与之前变量同名的新变量,而新变量会 **隐藏** 之前的变量。Rustacean 们称之为第一个变量被第二个 **隐藏** 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 `let` 关键字来多次隐藏,如下所示:
|
||||
正如在第二章猜猜看游戏的 [“比较猜测的数字和秘密数字”][comparing-the-guess-to-the-secret-number] 中所讲,我们可以定义一个与之前变量同名的新变量。Rustacean 们称之为第一个变量被第二个 **隐藏** 了,这意味着程序使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 `let` 关键字来多次隐藏,如下所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -106,20 +120,24 @@ fn main() {
|
||||
|
||||
let x = x + 1;
|
||||
|
||||
let x = x * 2;
|
||||
{
|
||||
let x = x * 2;
|
||||
println!("The value of x in the inner scope is: {}", x);
|
||||
}
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
这个程序首先将 `x` 绑定到值 `5` 上。接着通过 `let x =` 隐藏 `x`,获取初始值并加 `1`,这样 `x` 的值就变成 `6` 了。第三个 `let` 语句也隐藏了 `x`,将之前的值乘以 `2`,`x` 最终的值是 `12`。运行这个程序,它会有如下输出:
|
||||
这个程序首先将 `x` 绑定到值 `5` 上。接着通过 `let x =` 隐藏 `x`,获取初始值并加 `1`,这样 `x` 的值就变成 `6` 了。然后,在内部作用域内,第三个 `let` 语句也隐藏了 `x`,将之前的值乘以 `2`,`x` 得到的值是 `12`。当该作用域结束时,内部 shadowing 的作用域也结束了,`x` 又返回到 `6`。运行这个程序,它会有如下输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (file:///projects/variables)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
|
||||
Running `target/debug/variables`
|
||||
The value of x is: 12
|
||||
The value of x in the inner scope is: 12
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
隐藏与将变量标记为 `mut` 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 `let` 关键字,就会导致编译时错误。通过使用 `let`,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。
|
||||
@ -140,18 +158,26 @@ spaces = spaces.len();
|
||||
|
||||
这个错误说明,我们不能改变变量的类型:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling variables v0.1.0 (file:///projects/variables)
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:3:14
|
||||
|
|
||||
3 | spaces = spaces.len();
|
||||
| ^^^^^^^^^^^^ expected &str, found usize
|
||||
|
|
||||
= note: expected type `&str`
|
||||
found type `usize`
|
||||
| ^^^^^^^^^^^^ expected `&str`, found `usize`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `variables`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
现在我们已经了解了变量如何工作,让我们看看变量可以拥有的更多数据类型。
|
||||
|
||||
[comparing-the-guess-to-the-secret-number]:ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number
|
||||
[data-types]: ch03-02-data-types.html#data-types
|
||||
[comparing-the-guess-to-the-secret-number]:ch02-00-guessing-game-tutorial.html#比较猜测的数字和秘密数字
|
||||
[data-types]: ch03-02-data-types.html#数据类型
|
||||
[storing-values-with-variables]: ch02-00-guessing-game-tutorial.html#使用变量储存值
|
||||
[const-eval]: https://doc.rust-lang.org/reference/const_eval.html
|
@ -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 6598d3abac05ed1d0c45db92466ea49346d05e40
|
||||
> commit 05d9c4c2312a6542f792492d17a62f79ad6dfd7b
|
||||
|
||||
在 Rust 中,每一个值都属于某一个 **数据类型**(*data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型子集:标量(scalar)和复合(compound)。
|
||||
|
||||
@ -14,15 +14,21 @@ let guess: u32 = "42".parse().expect("Not a number!");
|
||||
|
||||
这里如果不添加类型注解,Rust 会显示如下错误,这说明编译器需要我们提供更多信息,来了解我们想要的类型:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
|
||||
error[E0282]: type annotations needed
|
||||
--> src/main.rs:2:9
|
||||
|
|
||||
2 | let guess = "42".parse().expect("Not a number!");
|
||||
| ^^^^^
|
||||
| |
|
||||
| cannot infer type for `_`
|
||||
| consider giving `guess` a type
|
||||
| ^^^^^ consider giving `guess` a type
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0282`.
|
||||
error: could not compile `no_type_annotations`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
你会看到其它数据类型的各种类型注解。
|
||||
@ -46,13 +52,13 @@ error[E0282]: type annotations needed
|
||||
| 128-bit | `i128` | `u128` |
|
||||
| arch | `isize` | `usize` |
|
||||
|
||||
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,数字是否需要有一个符号(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式(two’s complement representation)](https://en.wikipedia.org/wiki/Two%27s_complement) 存储。
|
||||
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,这个数字是否有可能是负数(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以[补码形式(two’s complement representation)](https://en.wikipedia.org/wiki/Two%27s_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 中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 `57u8`,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`。
|
||||
可以使用表格 3-2 中的任何一种形式编写数字字面值。请注意可以是多种数字类型的数字字面值允许使用类型后缀,例如 `57u8` 来指定类型,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`,它的值与你指定的 `1000` 相同。
|
||||
|
||||
<span class="caption">表格 3-2: Rust 中的整型字面值</span>
|
||||
|
||||
@ -64,13 +70,18 @@ error[E0282]: type annotations needed
|
||||
| Binary (二进制) | `0b1111_0000` |
|
||||
| Byte (单字节字符)(仅限于`u8`) | `b'A'` |
|
||||
|
||||
那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常就很好,数字类型默认是 `i32`:它通常是最快的,甚至在 64 位系统上也是。`isize` 或 `usize` 主要作为某些集合的索引。
|
||||
那么该使用哪种类型的数字呢?如果拿不定主意,Rust 的默认类型通常是个不错的起点,数字类型默认是 `i32`。`isize` 或 `usize` 主要作为某些集合的索引。
|
||||
|
||||
> ##### 整型溢出
|
||||
>
|
||||
> 比方说有一个 `u8` ,它可以存放从零到 `255` 的值。那么当你将其修改为 `256` 时会发生什么呢?这被称为 “整型溢出”(“integer overflow” ),关于这一行为 Rust 有一些有趣的规则。当在 debug 模式编译时,Rust 检查这类问题并使程序 *panic*,这个术语被 Rust 用来表明程序因错误而退出。第九章 [“`panic!` 与不可恢复的错误”][unrecoverable-errors-with-panic] 部分会详细介绍 panic。
|
||||
>
|
||||
> 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(*two’s complement wrapping*)的操作。简而言之,`256` 变成 `0`,`257` 变成 `1`,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,[`Wrapping`][wrapping]。
|
||||
> 在 release 构建中,Rust 不检测溢出,相反会进行一种被称为二进制补码包装(*two’s complement wrapping*)的操作。简而言之,值 `256` 变成 `0`,值 `257` 变成 `1`,依此类推。依赖整型溢出被认为是一种错误,即便可能出现这种行为。如果你确实需要这种行为,标准库中有一个类型显式提供此功能,[`Wrapping`][wrapping]。
|
||||
> 为了显式地处理溢出的可能性,你可以使用标准库在原生数值类型上提供的以下方法:
|
||||
> - 所有模式下都可以使用 `wrapping_*` 方法进行包装,如 `wrapping_add`
|
||||
> - 如果 `check_*` 方法出现溢出,则返回 `None`值
|
||||
> - 用 `overflowing_*` 方法返回值和一个布尔值,表示是否出现溢出
|
||||
> - 用 `saturating_*` 方法在值的最小值或最大值处进行饱和处理
|
||||
|
||||
#### 浮点型
|
||||
|
||||
@ -92,7 +103,7 @@ fn main() {
|
||||
|
||||
#### 数值运算
|
||||
|
||||
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。下面的代码展示了如何在 `let` 语句中使用它们:
|
||||
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。整数除法会向下舍入到最接近的整数。下面的代码展示了如何在 `let` 语句中使用它们:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -109,13 +120,14 @@ fn main() {
|
||||
|
||||
// 除法
|
||||
let quotient = 56.7 / 32.2;
|
||||
let floored = 2 / 3; // 结果为 0
|
||||
|
||||
// 取余
|
||||
let remainder = 43 % 5;
|
||||
}
|
||||
```
|
||||
|
||||
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,然后绑定给一个变量。附录 B 包含 Rust 提供的所有运算符的列表。
|
||||
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,然后绑定给一个变量。[附录 B][appendix_b]<!-- ignore --> 包含 Rust 提供的所有运算符的列表。
|
||||
|
||||
#### 布尔型
|
||||
|
||||
@ -201,6 +213,8 @@ fn main() {
|
||||
|
||||
这个程序创建了一个元组,`x`,并接着使用索引为每个元素创建新变量。跟大多数编程语言一样,元组的第一个索引值是 0。
|
||||
|
||||
没有任何值的元组 `()` 是一种特殊的类型,只有一个值,也写成 `()` 。该类型被称为 **单元类型**(*unit type*),而该值被称为 **单元值**(*unit value*)。如果表达式不返回任何其他值,则会隐式返回单元值。
|
||||
|
||||
#### 数组类型
|
||||
|
||||
另一个包含多个值的方式是 **数组**(*array*)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
|
||||
@ -242,7 +256,7 @@ let a = [3; 5];
|
||||
|
||||
##### 访问数组元素
|
||||
|
||||
数组是一整块分配在栈上的内存。可以使用索引来访问数组的元素,像这样:
|
||||
数组是可以在堆栈上分配的已知固定大小的单个内存块。可以使用索引来访问数组的元素,像这样:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -259,40 +273,53 @@ fn main() {
|
||||
|
||||
##### 无效的数组元素访问
|
||||
|
||||
如果我们访问数组结尾之后的元素会发生什么呢?比如你将上面的例子改成下面这样,这可以编译通过,不过在运行时会因错误而退出:
|
||||
如果我们访问数组结尾之后的元素会发生什么呢?比如你将上面的例子改成下面这样,它使用类似于第 2 章中的猜数字游戏的代码从用户那里获取数组索引:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,panics
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
let a = [1, 2, 3, 4, 5];
|
||||
let index = 10;
|
||||
|
||||
println!("Please enter an array index.");
|
||||
|
||||
let mut index = String::new();
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut index)
|
||||
.expect("Failed to read line");
|
||||
|
||||
let index: usize = index
|
||||
.trim()
|
||||
.parse()
|
||||
.expect("Index entered was not a number");
|
||||
|
||||
let element = a[index];
|
||||
|
||||
println!("The value of element is: {}", element);
|
||||
println!(
|
||||
"The value of the element at index {} is: {}",
|
||||
index, element
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
使用 `cargo run` 运行代码后会产生如下结果:
|
||||
此代码编译成功。如果您使用 `cargo run` 运行此代码并输入 0、1、2、3 或 4,程序将在数组中的索引处打印出相应的值。如果你输入一个超过数组末端的数字,如 10,你会看到这样的输出:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling arrays v0.1.0 (file:///projects/arrays)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
Running `target/debug/arrays`
|
||||
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is
|
||||
10', src/main.rs:5:19
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
```console
|
||||
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrac
|
||||
```
|
||||
|
||||
编译并没有产生任何错误,不过程序会出现一个 **运行时**(*runtime*)错误并且不会成功退出。当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 *panic*,这是 Rust 术语,它用于程序因为错误而退出的情况。
|
||||
程序在索引操作中使用一个无效的值时导致 **运行时** 错误。程序带着错误信息退出,并且没有执行最后的 `println!` 语句。当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 *panic*,这是 Rust 术语,它用于程序因为错误而退出的情况。这种检查必须在运行时进行,特别是在这种情况下,因为编译器不可能知道用户在以后运行代码时将输入什么值。
|
||||
|
||||
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中,并没有进行这类检查,这样当提供了一个不正确的索引时,就会访问无效的内存。通过立即退出而不是允许内存访问并继续执行,Rust 让你避开此类错误。第九章会讨论更多 Rust 的错误处理。
|
||||
|
||||
[comparing-the-guess-to-the-secret-number]:
|
||||
ch02-00-guessing-game-tutorial.html#comparing-the-guess-to-the-secret-number
|
||||
[control-flow]: ch03-05-control-flow.html#control-flow
|
||||
[strings]: ch08-02-strings.html#storing-utf-8-encoded-text-with-strings
|
||||
[control-flow]: ch03-05-control-flow.html#控制流
|
||||
[strings]: ch08-02-strings.html#使用字符串存储-utf-8-编码的文本
|
||||
[unrecoverable-errors-with-panic]: ch09-01-unrecoverable-errors-with-panic.html
|
||||
[wrapping]: ../std/num/struct.Wrapping.html
|
||||
[wrapping]: https://doc.rust-lang.org/std/num/struct.Wrapping.html
|
||||
[appendix_b]: appendix-02-operators.md
|
@ -2,9 +2,9 @@
|
||||
|
||||
> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/main/src/ch03-03-how-functions-work.md)
|
||||
> <br>
|
||||
> commit 669a909a199bc20b913703c6618741d8b6ce1552
|
||||
> commit 05d9c4c2312a6542f792492d17a62f79ad6dfd7b
|
||||
|
||||
函数遍布于 Rust 代码中。你已经见过语言中最重要的函数之一:`main` 函数,它是很多程序的入口点。你也见过 `fn` 关键字,它用来声明新函数。
|
||||
函数在 Rust 代码中非常普遍。你已经见过语言中最重要的函数之一:`main` 函数,它是很多程序的入口点。你也见过 `fn` 关键字,它用来声明新函数。
|
||||
|
||||
Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这是一个包含函数定义示例的程序:
|
||||
|
||||
@ -31,7 +31,7 @@ Rust 中的函数定义以 `fn` 开始并在函数名后跟一对圆括号。大
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.28 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
|
||||
Running `target/debug/functions`
|
||||
Hello, world!
|
||||
Another function.
|
||||
@ -62,7 +62,7 @@ fn another_function(x: i32) {
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.21 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.21s
|
||||
Running `target/debug/functions`
|
||||
The value of x is: 5
|
||||
```
|
||||
@ -77,29 +77,27 @@ The value of x is: 5
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
another_function(5, 6);
|
||||
print_labeled_measurement(5, 'h');
|
||||
}
|
||||
|
||||
fn another_function(x: i32, y: i32) {
|
||||
println!("The value of x is: {}", x);
|
||||
println!("The value of y is: {}", y);
|
||||
fn print_labeled_measurement(value: i32, unit_label: char) {
|
||||
println!("The measurement is: {}{}", value, unit_label);
|
||||
}
|
||||
```
|
||||
|
||||
这个例子创建了有两个参数的函数,都是 `i32` 类型。函数打印出了这两个参数的值。注意函数的参数类型并不一定相同,这个例子中只是碰巧相同罢了。
|
||||
这个例子创建了一个名为 `print_labeled_measurement` 的函数,它有两个参数。第一个参数名为 `value`, 类型是 `i32`。第二个参数是 `unit_label` ,类型是 `char`。然后,该函数打印包含 `value` 和 `unit_label` 的文本。
|
||||
|
||||
尝试运行代码。使用上面的例子替换当前 *functions* 项目的 *src/main.rs* 文件,并用 `cargo run` 运行它:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
|
||||
Running `target/debug/functions`
|
||||
The value of x is: 5
|
||||
The value of y is: 6
|
||||
The measurement is: 5h
|
||||
```
|
||||
|
||||
因为我们使用 `5` 作为 `x` 的值,`6` 作为 `y` 的值来调用函数,因此打印出这两个字符串及相应的值。
|
||||
因为我们使用 `5` 作为 `value` 的值,`h` 作为 `unit_label` 的值来调用函数,所以程序输出包含这些值。
|
||||
|
||||
### 包含语句和表达式的函数体
|
||||
|
||||
@ -136,18 +134,42 @@ fn main() {
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
error[E0658]: `let` expressions in this position are experimental
|
||||
--> src/main.rs:2:14
|
||||
|
|
||||
2 | let x = (let y = 6);
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information
|
||||
= help: you can write `matches!(<expr>, <pattern>)` instead of `let <pattern> = <expr>`
|
||||
|
||||
error: expected expression, found statement (`let`)
|
||||
--> src/main.rs:2:14
|
||||
|
|
||||
2 | let x = (let y = 6);
|
||||
| ^^^
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: variable declaration using `let` is a statement
|
||||
|
||||
warning: unnecessary parentheses around assigned value
|
||||
--> src/main.rs:2:13
|
||||
|
|
||||
2 | let x = (let y = 6);
|
||||
| ^^^^^^^^^^^ help: remove these parentheses
|
||||
|
|
||||
= note: `#[warn(unused_parens)]` on by default
|
||||
|
||||
error: aborting due to 2 previous errors; 1 warning emitted
|
||||
|
||||
For more information about this error, try `rustc --explain E0658`.
|
||||
error: could not compile `functions`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
`let y = 6` 语句并不返回值,所以没有可以绑定到 `x` 上的值。这与其他语言不同,例如 C 和 Ruby,它们的赋值语句会返回所赋的值。在这些语言中,可以这么写 `x = y = 6`,这样 `x` 和 `y` 的值都是 `6`;Rust 中不能这样写。
|
||||
|
||||
表达式会计算出一些值,并且你将编写的大部分 Rust 代码是由表达式组成的。考虑一个简单的数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在示例 3-1 中,语句 `let y = 6;` 中的 `6` 是一个表达式,它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),`{}`,也是一个表达式,例如:
|
||||
表达式会计算出一个值,并且你将编写的大部分 Rust 代码是由表达式组成的。考虑一个数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在示例 3-1 中,语句 `let y = 6;` 中的 `6` 是一个表达式,它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),`{}`,也是一个表达式,例如:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -198,7 +220,7 @@ fn main() {
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
|
||||
Running `target/debug/functions`
|
||||
The value of x is: 5
|
||||
```
|
||||
@ -246,18 +268,24 @@ fn plus_one(x: i32) -> i32 {
|
||||
运行代码会产生一个错误,如下:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling functions v0.1.0 (file:///projects/functions)
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:7:28
|
||||
--> src/main.rs:7:24
|
||||
|
|
||||
7 | fn plus_one(x: i32) -> i32 {
|
||||
| ____________________________^
|
||||
8 | | x + 1;
|
||||
| | - help: consider removing this semicolon
|
||||
9 | | }
|
||||
| |_^ expected i32, found ()
|
||||
|
|
||||
= note: expected type `i32`
|
||||
found type `()`
|
||||
7 | fn plus_one(x: i32) -> i32 {
|
||||
| -------- ^^^ expected `i32`, found `()`
|
||||
| |
|
||||
| implicitly returns `()` as its body has no tail or `return` expression
|
||||
8 | x + 1;
|
||||
| - help: consider removing this semicolon
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `functions`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 `plus_one` 的定义说明它要返回一个 `i32` 类型的值,不过语句并不会返回值,使用空元组 `()` 表示不返回值。因为不返回值与函数定义相矛盾,从而出现一个错误。在输出中,Rust 提供了一条信息,可能有助于纠正这个错误:它建议删除分号,这会修复这个错误。
|
||||
主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 `plus_one` 的定义说明它要返回一个 `i32` 类型的值,不过语句并不会返回值,使用单位类型 `()` 表示不返回值。因为不返回值与函数定义相矛盾,从而出现一个错误。在输出中,Rust 提供了一条信息,可能有助于纠正这个错误:它建议删除分号,这会修复这个错误。
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/main/src/ch03-04-comments.md)
|
||||
> <br>
|
||||
> commit 75a77762ea2d2ab7fa1e9ef733907ed727c85651
|
||||
> commit 25a1530ccbf0a79c8df2920ee2af8beb106122e8
|
||||
|
||||
所有程序员都力求使其代码易于理解,不过有时还需要提供额外的解释。在这种情况下,程序员在源码中留下记录,或者 **注释**(*comments*),编译器会忽略它们,不过阅读代码的人可能觉得有用。
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
// hello, world
|
||||
```
|
||||
|
||||
在 Rust 中,注释必须以两道斜杠开始,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 `//`,像这样:
|
||||
在 Rust 中,惯用的注释样式是以两个斜杠开始注释,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 `//`,像这样:
|
||||
|
||||
```rust
|
||||
// So we’re doing something complicated here, long enough that we need
|
||||
|
@ -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 af34ac954a6bd7fc4a8bbcc5c9685e23c5af87da
|
||||
> commit 4b86611b0e63151f6e166edc9ecf870d553e1f09
|
||||
|
||||
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
|
||||
|
||||
@ -34,10 +34,10 @@ fn main() {
|
||||
|
||||
尝试运行代码,应该能看到如下输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
|
||||
Running `target/debug/branches`
|
||||
condition was true
|
||||
```
|
||||
@ -50,10 +50,10 @@ let number = 7;
|
||||
|
||||
再次运行程序并查看输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
|
||||
Running `target/debug/branches`
|
||||
condition was false
|
||||
```
|
||||
@ -74,15 +74,21 @@ fn main() {
|
||||
|
||||
这里 `if` 条件的值是 `3`,Rust 抛出了一个错误:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.rs:4:8
|
||||
|
|
||||
4 | if number {
|
||||
| ^^^^^^ expected bool, found integer
|
||||
|
|
||||
= note: expected type `bool`
|
||||
found type `{integer}`
|
||||
| ^^^^^^ expected `bool`, found integer
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `branches`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
这个错误表明 Rust 期望一个 `bool` 却得到了一个整数。不像 Ruby 或 JavaScript 这样的语言,Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 `if` 的条件。例如,如果想要 `if` 代码块只在一个数字不等于 `0` 时执行,可以把 `if` 表达式修改成下面这样:
|
||||
@ -125,7 +131,7 @@ fn main() {
|
||||
|
||||
这个程序有四个可能的执行路径。运行后应该能看到如下输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.31 secs
|
||||
@ -160,10 +166,10 @@ fn main() {
|
||||
|
||||
`number` 变量将会绑定到表示 `if` 表达式结果的值上。运行这段代码看看会出现什么:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
|
||||
Running `target/debug/branches`
|
||||
The value of number is: 5
|
||||
```
|
||||
@ -188,20 +194,23 @@ fn main() {
|
||||
|
||||
当编译这段代码时,会得到一个错误。`if` 和 `else` 分支的值类型是不相容的,同时 Rust 也准确地指出在程序中的何处发现的这个问题:
|
||||
|
||||
```text
|
||||
error[E0308]: if and else have incompatible types
|
||||
--> src/main.rs:4:18
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling branches v0.1.0 (file:///projects/branches)
|
||||
error[E0308]: `if` and `else` have incompatible types
|
||||
--> src/main.rs:4:44
|
||||
|
|
||||
4 | let number = if condition {
|
||||
| __________________^
|
||||
5 | | 5
|
||||
6 | | } else {
|
||||
7 | | "six"
|
||||
8 | | };
|
||||
| |_____^ expected integer, found &str
|
||||
|
|
||||
= note: expected type `{integer}`
|
||||
found type `&str`
|
||||
4 | let number = if condition { 5 } else { "six" };
|
||||
| - ^^^^^ expected integer, found `&str`
|
||||
| |
|
||||
| expected because of this
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0308`.
|
||||
error: could not compile `branches`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
`if` 代码块中的表达式返回一个整数,而 `else` 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 `number` 变量的类型,这样它就可以在编译时验证在每处使用的 `number` 变量的类型是有效的。如果`number`的类型仅在运行时确定,则Rust无法做到这一点;且编译器必须跟踪每一个变量的多种假设类型,那么它就会变得更加复杂,对代码的保证也会减少。
|
||||
@ -230,10 +239,10 @@ fn main() {
|
||||
|
||||
当运行这个程序时,我们会看到连续的反复打印 `again!`,直到我们手动停止程序。大部分终端都支持一个快捷键,<span class="keystroke">ctrl-c</span>,来终止一个陷入无限循环的程序。尝试一下:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling loops v0.1.0 (file:///projects/loops)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.29 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
|
||||
Running `target/debug/loops`
|
||||
again!
|
||||
again!
|
||||
@ -244,7 +253,53 @@ again!
|
||||
|
||||
符号 `^C` 代表你在这按下了<span class="keystroke">ctrl-c</span>。在 `^C` 之后你可能看到也可能看不到 `again!` ,这取决于在接收到终止信号时代码执行到了循环的何处。
|
||||
|
||||
幸运的是,Rust 提供了另一种更可靠的退出循环的方式。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第二章猜猜看游戏的 [“猜测正确后退出”][quitting-after-a-correct-guess] 部分使用过它来在用户猜对数字赢得游戏后退出程序。
|
||||
幸运的是,Rust 提供了一种从代码中跳出循环的方法。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第二章猜猜看游戏的 [“猜测正确后退出”][quitting-after-a-correct-guess] 部分使用过它来在用户猜对数字赢得游戏后退出程序。
|
||||
|
||||
我们在猜谜游戏中也使用了 `continue`。循环中的 `continue` 关键字告诉程序跳过这个循环迭代中的任何剩余代码,并转到下一个迭代。
|
||||
|
||||
如果存在嵌套循环,`break` 和 `continue` 应用于此时最内层的循环。你可以选择在一个循环上指定一个 **循环标签**(*loop label*),然后将标签与 `break` 或 `continue` 一起使用,使这些关键字应用于已标记的循环而不是最内层的循环。下面是一个包含两个嵌套循环的示例
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut count = 0;
|
||||
'counting_up: loop {
|
||||
println!("count = {}", count);
|
||||
let mut remaining = 10;
|
||||
|
||||
loop {
|
||||
println!("remaining = {}", remaining);
|
||||
if remaining == 9 {
|
||||
break;
|
||||
}
|
||||
if count == 2 {
|
||||
break 'counting_up;
|
||||
}
|
||||
remaining -= 1;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
println!("End count = {}", count);
|
||||
}
|
||||
```
|
||||
|
||||
外层循环有一个标签 `counting_ up`,它将从 0 数到 2。没有标签的内部循环从 10 向下数到 9。第一个没有指定标签的 `break` 将只退出内层循环。`break'counting_up;` 语句将退出外层循环。这个代码打印:
|
||||
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling loops v0.1.0 (file:///projects/loops)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
|
||||
Running `target/debug/loops`
|
||||
count = 0
|
||||
remaining = 10
|
||||
remaining = 9
|
||||
count = 1
|
||||
remaining = 10
|
||||
remaining = 9
|
||||
count = 2
|
||||
remaining = 10
|
||||
End count = 2
|
||||
```
|
||||
|
||||
#### 从循环返回
|
||||
|
||||
@ -317,10 +372,10 @@ fn main() {
|
||||
|
||||
这里,代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5` 不再为真)。运行这段代码会打印出数组中的每一个元素:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling loops v0.1.0 (file:///projects/loops)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
|
||||
Running `target/debug/loops`
|
||||
the value is: 10
|
||||
the value is: 20
|
||||
@ -331,7 +386,7 @@ the value is: 50
|
||||
|
||||
数组中的所有五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 `5`,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
|
||||
|
||||
但这个过程很容易出错;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
|
||||
但这个过程很容易出错;如果索引长度或测试条件不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环进行条件检查,以确定在循环的每次迭代中索引是否在数组的边界内。
|
||||
|
||||
作为更简洁的替代方案,可以使用 `for` 循环来对一个集合的每个元素执行一些代码。`for` 循环看起来如示例 3-5 所示:
|
||||
|
||||
@ -351,7 +406,7 @@ fn main() {
|
||||
|
||||
当运行这段代码时,将看到与示例 3-4 一样的输出。更为重要的是,我们增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug。
|
||||
|
||||
例如,在示例 3-4 的代码中,如果从数组 `a` 中移除一个元素但忘记将条件更新为 `while index < 4`,代码将会 panic。使用 `for` 循环的话,就不需要惦记着在改变数组元素个数时修改其他的代码了。
|
||||
例如,在示例 3-4 的代码中,如果你将 `a` 数组的定义改为有四个元素,但忘记将条件更新为 `while index < 4`,代码将会 panic。使用 `for` 循环的话,就不需要惦记着在改变数组元素个数时修改其他的代码了。
|
||||
|
||||
`for` 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 `while` 循环的倒计时例子,大部分 Rustacean 也会使用 `for` 循环。这么做的方式是使用 `Range`,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。
|
||||
|
||||
@ -381,6 +436,6 @@ fn main() {
|
||||
当你准备好继续的时候,让我们讨论一个其他语言中 **并不** 常见的概念:所有权(ownership)。
|
||||
|
||||
[comparing-the-guess-to-the-secret-number]:
|
||||
ch02-00-guessing-game-tutorial.html#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
|
||||
ch02-00-guessing-game-tutorial.html#猜测正确后退出
|
||||
|
@ -188,7 +188,7 @@ pub fn notify(item: impl Summary) {
|
||||
|
||||
#### Trait Bound 语法
|
||||
|
||||
`impl Trait` 语法适用于直观的例子,它不过是一个较长形式的语法糖。这被称为 *trait bound*,这看起来像:
|
||||
`impl Trait` 语法适用于直观的例子,它实际上是一种较长形式语法的语法糖。我们称为 *trait bound*,它看起来像:
|
||||
|
||||
```rust,ignore
|
||||
pub fn notify<T: Summary>(item: T) {
|
||||
@ -245,7 +245,7 @@ fn some_function<T, U>(t: T, u: U) -> i32
|
||||
{
|
||||
```
|
||||
|
||||
这个函数签名就显得不那么杂乱,函数名、参数列表和返回值类型都离得很近,看起来类似没有很多 trait bounds 的函数。
|
||||
这个函数签名就显得不那么杂乱,函数名、参数列表和返回值类型都离得很近,看起来跟没有那么多 trait bounds 的函数很像。
|
||||
|
||||
### 返回实现了 trait 的类型
|
||||
|
||||
|
@ -277,7 +277,7 @@ Problem parsing arguments: not enough arguments
|
||||
|
||||
### 从 `main` 提取逻辑
|
||||
|
||||
现在我们完成了配置解析的重构:让我们转向程序的逻辑。正如 [“二进制项目的关注分离”](#separation-of-concerns-for-binary-projects) 部分所展开的讨论,我们将提取一个叫做 `run` 的函数来存放目前 `main `函数中不属于设置配置或处理错误的所有逻辑。一旦完成这些,`main` 函数将简明得足以通过观察来验证,而我们将能够为所有其他逻辑编写测试。
|
||||
现在我们完成了配置解析的重构:让我们转向程序的逻辑。正如 [“二进制项目的关注分离”](#separation-of-concerns-for-binary-projects) 部分所展开的讨论,我们将提取一个叫做 `run` 的函数来存放目前 `main` 函数中不属于设置配置或处理错误的所有逻辑。一旦完成这些,`main` 函数将简明得足以通过观察来验证,而我们将能够为所有其他逻辑编写测试。
|
||||
|
||||
示例 12-11 展示了提取出来的 `run` 函数。目前我们只进行小的增量式的提取函数的改进。我们仍将在 *src/main.rs* 中定义这个函数:
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
目前为止,我们将所有的输出都 `println!` 到了终端。大部分终端都提供了两种输出:**标准输出**(*standard output*,`stdout`)对应一般信息,**标准错误**(*standard error*,`stderr`)则用于错误信息。这种区别允许用户选择将程序正常输出定向到一个文件中并仍将错误信息打印到屏幕上。
|
||||
|
||||
但是 `println!` 函数只能够打印到标准输出,所以我们必需使用其他方法来打印到标准错误。
|
||||
但是 `println!` 函数只能够打印到标准输出,所以我们必须使用其他方法来打印到标准错误。
|
||||
|
||||
### 检查错误应该写入何处
|
||||
|
||||
|
@ -98,7 +98,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
|
||||
### 使用 `pub use` 导出合适的公有 API
|
||||
|
||||
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
|
||||
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
|
||||
|
||||
公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
|
||||
|
||||
|
@ -295,9 +295,8 @@ fn main() {
|
||||
```
|
||||
|
||||
<span class="caption">示例 19-9: 定义和使用一个不可变静态变量</span>
|
||||
静态变量类似于常量,我们在第三章的 "变量和常量的区别 "一节中讨论过。静态变量的名字按惯例采用SCREAMING_SNAKE_CASE。静态变量只能存储具有 "静态寿命 "的引用,这意味着Rust编译器可以计算出其寿命,我们不需要明确注释。访问一个不可变的静态变量是安全的。
|
||||
|
||||
`static` 变量类似于第三章 [“变量和常量的区别”][differences-between-variables-and-constants] 部分讨论的常量。通常静态变量的名称采用 `SCREAMING_SNAKE_CASE` 写法。静态变量只能储存拥有 `'static` 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。
|
||||
静态(`static`)变量类似于第三章 [“变量和常量的区别”][differences-between-variables-and-constants] 部分讨论的常量。通常静态变量的名称采用 `SCREAMING_SNAKE_CASE` 写法。静态变量只能储存拥有 `'static` 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。
|
||||
|
||||
常量与不可变静态变量可能看起来很类似,不过一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。
|
||||
|
||||
@ -331,7 +330,7 @@ fn main() {
|
||||
|
||||
### 实现不安全 trait
|
||||
|
||||
`unsafe` 的另一个操作用例是实现不安全 trait。当至少有一个方法中包含编译器不能验证的不变量时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示:
|
||||
`unsafe` 的另一个操作用例是实现不安全 trait。当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示:
|
||||
|
||||
```rust
|
||||
unsafe trait Foo {
|
||||
@ -360,8 +359,8 @@ unsafe impl Foo for i32 {
|
||||
[dangling-references]:
|
||||
ch04-02-references-and-borrowing.html#dangling-references
|
||||
[differences-between-variables-and-constants]:
|
||||
ch03-01-variables-and-mutability.html#differences-between-variables-and-constants
|
||||
ch03-01-variables-and-mutability.html#变量和常量的区别
|
||||
[extensible-concurrency-with-the-sync-and-send-traits]:
|
||||
ch16-04-extensible-concurrency-sync-and-send.html#extensible-concurrency-with-the-sync-and-send-traits
|
||||
ch16-04-extensible-concurrency-sync-and-send.html#使用-sync-和-send-trait-的可扩展并发
|
||||
[the-slice-type]: ch04-03-slices.html#the-slice-type
|
||||
[reference]: https://doc.rust-lang.org/reference/items/unions.html
|
||||
|
Loading…
Reference in New Issue
Block a user