mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-14 21:11:31 +08:00
Merge branch 'master' of https://github.com/KaiserY/trpl-zh-cn
This commit is contained in:
commit
72930b80d7
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 KaiserY
|
||||
Copyright (c) 2017-2018 Rust 中文社区
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit 7480e811ab5ad8d53a5b854d9b0c7a5a4f58499f
|
||||
|
||||
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中常用的一些概念,并通过真实的程序来展示如何运用它们。你将会学到更多诸如 `let`、`match`、方法、关联函数、外部 crate 等很多的知识!后继章节会深入探索这些概念的细节。在这一章,我们将练习基础。
|
||||
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中一些常用概念,并通过真实的程序来展示如何运用它们。你将会学到 `let`、`match`、方法、关联函数、外部 crate 等知识!后续章节会深入探讨这些概念的细节。在这一章,我们将做基础练习。
|
||||
|
||||
我们会实现一个经典的新手编程问题:猜猜看游戏。它是这么工作的:程序将会随机生成一个 1 到 100 之间的随机整数。接着它会请玩家猜一个数并输入,然后提示猜测是大了还是小了。如果猜对了,它会打印祝贺信息并退出。
|
||||
|
||||
@ -44,22 +44,23 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
现在编译 “Hello, world!” 程序,使用 `cargo run` 编译运行一步到位:
|
||||
现在使用 `cargo run` 命令,一步完成 “Hello, world!” 程序的编译和运行:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.50 secs
|
||||
Running `target/debug/guessing_game`
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
`run` 命令适合用于需要快速迭代的项目,而这个游戏便是这样的项目:我们需要在下一步迭代之前快速测试每一步。
|
||||
当你需要在项目中快速迭代时,`run` 命令就能派上用场,正如我们在这个游戏项目中做的,在下一次迭代之前快速测试每一次迭代。
|
||||
|
||||
重新打开 *src/main.rs* 文件。我们将会在这个文件中编写全部的代码。
|
||||
|
||||
## 处理一次猜测
|
||||
|
||||
程序的第一部分请求和处理用户输入,并检查输入是否符合预期的格式。首先,允许用户输入猜测。在 *src/main.rs* 中输入示例 2-1 中的代码。
|
||||
猜猜看程序的第一部分请求和处理用户输入,并检查输入是否符合预期的格式。首先,允许玩家输入猜测。在 *src/main.rs* 中输入示例 2-1 中的代码。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -82,13 +83,13 @@ fn main() {
|
||||
|
||||
<span class="caption">示例 2-1:获取用户猜测并打印的代码</span>
|
||||
|
||||
这些代码包含很多信息,我们一行一行地过一遍。为了获取用户输入并打印结果作为输出,我们需要将 `io`(输入/输出)库引入当前作用域。`io` 库来自于标准库(也被称为`std`):
|
||||
这些代码包含很多信息,我们一行一行地过一遍。为了获取用户输入并打印结果作为输出,我们需要将 `io`(输入/输出)库引入当前作用域。`io` 库来自于标准库(也被称为 `std`):
|
||||
|
||||
```rust,ignore
|
||||
use std::io;
|
||||
```
|
||||
|
||||
Rust 默认只在每个程序的 [*prelude*][prelude]<!-- ignore --> 中引入少量类型。如果需要的类型不在 prelude 中,你必须使用一个 `use` 语句显式地将其引入作用域。`std::io` 库提供很多 `io` 相关的功能,比如接受用户输入的功能。
|
||||
默认情况下,Rust 将 [*prelude*][prelude]<!-- ignore --> 模块中少量的类型引入到每个程序的作用域中。如果需要的类型不在 prelude 中,你必须使用 `use` 语句显式地将其引入作用域。`std::io` 库提供很多有用的功能,包括接收用户输入的功能。
|
||||
|
||||
[prelude]: https://doc.rust-lang.org/std/prelude/index.html
|
||||
|
||||
@ -112,7 +113,7 @@ println!("Please input your guess.");
|
||||
|
||||
### 使用变量储存值
|
||||
|
||||
接下来,创建一个地方储存用户输入,像这样:
|
||||
接下来,创建一个储存用户输入的地方,像这样:
|
||||
|
||||
```rust,ignore
|
||||
let mut guess = String::new();
|
||||
@ -131,7 +132,7 @@ let foo = 5; // immutable
|
||||
let mut bar = 5; // mutable
|
||||
```
|
||||
|
||||
> 注意:`//` 语法开始一个持续到本行的结尾的注释。Rust 忽略注释中的所有内容。
|
||||
> 注意:`//` 语法开始一个注释,持续到行尾。Rust 忽略注释中的所有内容,将在第三章中详细介绍注释。
|
||||
|
||||
让我们回到猜猜看程序中。现在我们知道了 `let mut guess` 会引入一个叫做 `guess` 的可变变量。等号(`=`)的右边是 `guess` 所绑定的值,它是 `String::new` 的结果,这个函数会返回一个 `String` 的新实例。[`String`][string]<!-- ignore --> 是一个标准库提供的字符串类型,它是 UTF-8 编码的可增长文本块。
|
||||
|
||||
@ -139,7 +140,7 @@ let mut bar = 5; // mutable
|
||||
|
||||
`::new` 那一行的 `::` 语法表明 `new` 是 `String` 类型的一个 **关联函数**(*associated function*)。关联函数是针对类型实现的,在这个例子中是 `String`,而不是 `String` 的某个特定实例。一些语言中把它称为 **静态方法**(*static method*)。
|
||||
|
||||
`new` 函数创建了一个新的空 `String`,你会在很多类型上发现 `new` 函数,这是创建类型实例的惯用函数名。
|
||||
`new` 函数创建了一个新的空字符串,你会发现很多类型上有 `new` 函数,因为它是创建类型实例的惯用函数名。
|
||||
|
||||
总结一下,`let mut guess = String::new();` 这一行创建了一个可变变量,当前它绑定到一个新的 `String` 空实例上。
|
||||
|
||||
@ -160,7 +161,7 @@ io::stdin().read_line(&mut guess)
|
||||
|
||||
`read_line` 的工作是,无论用户在标准输入中键入什么内容,都将其存入一个字符串中,因此它需要字符串作为参数。这个字符串参数应该是可变的,以便 `read_line` 将用户输入附加上去。
|
||||
|
||||
`&` 表示这个参数是一个 **引用**(*reference*),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的操纵引用。完成当前程序并不需要了解如此多细节。现在,我们只需知道它像变量一样,默认是不可变的,需要写成 `&mut guess` 而不是 `&guess` 来使其可变。(第四章会更全面的解释引用。)
|
||||
`&` 表示这个参数是一个 **引用**(*reference*),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的操纵引用。完成当前程序并不需要了解如此多细节。现在,我们只需知道它像变量一样,默认是不可变的。因此,需要写成 `&mut guess` 来使其可变,而不是 `&guess`。(第四章会更全面的解释引用。)
|
||||
|
||||
### 使用 `Result` 类型来处理潜在的错误
|
||||
|
||||
@ -170,13 +171,13 @@ io::stdin().read_line(&mut guess)
|
||||
.expect("Failed to read line");
|
||||
```
|
||||
|
||||
当使用 `.foo()` 语法调用方法时,通过换行并缩进来把长行拆开是明智的。我们完全可以这样写:
|
||||
当使用 `.foo()` 语法调用方法时,通过换行加缩进来把长行拆开是明智的。我们完全可以这样写:
|
||||
|
||||
```rust,ignore
|
||||
io::stdin().read_line(&mut guess).expect("Failed to read line");
|
||||
```
|
||||
|
||||
不过,过长的行难以阅读,所以最好拆开来写,两行代码两个方法调用。现在来看看这行代码干了什么。
|
||||
不过,过长的行难以阅读,所以最好拆开来写,两个方法调用占两行。现在来看看这行代码干了什么。
|
||||
|
||||
之前提到了 `read_line` 将用户输入附加到传递给它的字符串中,不过它也返回一个值——在这个例子中是 [`io::Result`][ioresult]<!-- ignore -->。Rust 标准库中有很多叫做 `Result` 的类型。一个 [`Result`][result]<!-- ignore --> 泛型以及对应子模块的特定版本,比如 `io::Result`。
|
||||
|
||||
@ -187,9 +188,9 @@ io::stdin().read_line(&mut guess).expect("Failed to read line");
|
||||
|
||||
[enums]: ch06-00-enums.html
|
||||
|
||||
对于 `Result`,它的成员是 `Ok` 或 `Err`,`Ok` 成员表示操作成功,内部包含成功时产生的值。`Err` 成员则意味着操作失败,并且包含失败的前因后果。
|
||||
`Result` 的成员是 `Ok` 和 `Err`,`Ok` 成员表示操作成功,内部包含成功时产生的值。`Err` 成员则意味着操作失败,并且包含失败的前因后果。
|
||||
|
||||
这些 `Result` 类型的作用是编码错误处理信息。`Result` 类型的值,像其他类型一样,拥有定义于其上的方法。`io::Result` 的实例拥有 [`expect` 方法][expect]<!-- ignore -->。如果 `io::Result` 实例的值是 `Err`,`expect` 会导致程序崩溃,并显示当做参数传递给 `expect` 的信息。如果 `read_line` 方法返回 `Err`,则可能是来源于底层操作系统错误的结果。如果 `io::Result` 实例的值是 `Ok`,`expect` 会获取 `Ok` 中的值并原样返回。在本例中,这个值是用户输入到标准输入中的字节的数量。
|
||||
这些 `Result` 类型的作用是编码错误处理信息。`Result` 类型的值,像其他类型一样,拥有定义于其上的方法。`io::Result` 的实例拥有 [`expect` 方法][expect]<!-- ignore -->。如果 `io::Result` 实例的值是 `Err`,`expect` 会导致程序崩溃,并显示当做参数传递给 `expect` 的信息。如果 `read_line` 方法返回 `Err`,则可能是来源于底层操作系统错误的结果。如果 `io::Result` 实例的值是 `Ok`,`expect` 会获取 `Ok` 中的值并原样返回。在本例中,这个值是用户输入到标准输入中的字节数。
|
||||
|
||||
[expect]: https://doc.rust-lang.org/std/result/enum.Result.html#method.expect
|
||||
|
||||
@ -255,9 +256,9 @@ You guessed: 6
|
||||
|
||||
### 使用 crate 来增加更多功能
|
||||
|
||||
记住 *crate* 是一个 Rust 代码的包。我们正在构建的项目是一个 **二进制 crate**,它生成一个可执行文件。 `rand` crate 是一个 **库 crate**,库 crate 可以包含任意能被其他程序使用的代码。
|
||||
记住,*crate* 是一个 Rust 代码包。我们正在构建的项目是一个 **二进制 crate**,它生成一个可执行文件。 `rand` crate 是一个 **库 crate**,库 crate 可以包含任意能被其他程序使用的代码。
|
||||
|
||||
Cargo 对外部 crate 的运用是其真正闪光的地方。在我们使用 `rand` 编写代码之前,需要编辑 *Cargo.toml* ,声明 `rand` 作为一个依赖。现在打开这个文件并在底部的 `[dependencies]` 部分标题之下添加:
|
||||
Cargo 对外部 crate 的运用是其真正闪光的地方。在我们使用 `rand` 编写代码之前,需要修改 *Cargo.toml* 文件,引入一个 `rand` 依赖。现在打开这个文件并在底部的 `[dependencies]` 片段标题之下添加:
|
||||
|
||||
<span class="filename">文件名: Cargo.toml</span>
|
||||
|
||||
@ -267,7 +268,7 @@ Cargo 对外部 crate 的运用是其真正闪光的地方。在我们使用 `ra
|
||||
rand = "0.3.14"
|
||||
```
|
||||
|
||||
在 *Cargo.toml* 文件中,标题以及之后的内容属同一个部分,直到遇到下一个标题才开始新的部分。`[dependencies]` 部分告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 `0.3.14` 来指定 `rand` crate。Cargo 理解[语义化版本(Semantic Versioning)][semver]<!-- ignore -->(有时也称为 *SemVer*),这是一种定义版本号的标准。`0.3.14` 事实上是 `^0.3.14` 的简写,它表示 “任何与 0.3.14 版本公有 API 相兼容的版本”。
|
||||
在 *Cargo.toml* 文件中,标题以及之后的内容属同一个片段,直到遇到下一个标题才开始新的片段。`[dependencies]` 片段告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 `0.3.14` 来指定 `rand` crate。Cargo 理解[语义化版本(Semantic Versioning)][semver]<!-- ignore -->(有时也称为 *SemVer*),这是一种定义版本号的标准。`0.3.14` 事实上是 `^0.3.14` 的简写,它表示 “任何与 0.3.14 版本公有 API 相兼容的版本”。
|
||||
|
||||
[semver]: http://semver.org
|
||||
|
||||
@ -284,7 +285,7 @@ $ cargo build
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
|
||||
```
|
||||
|
||||
<span class="caption">示例 2-2: 增加 rand crate 作为依赖之后运行 `cargo build` 的输出</span>
|
||||
<span class="caption">示例 2-2: 将 rand crate 添加为依赖之后运行 `cargo build` 的输出</span>
|
||||
|
||||
可能会出现不同的版本号(多亏了语义化版本,它们与代码是兼容的!),同时显示顺序也可能会有所不同。
|
||||
|
||||
@ -292,7 +293,7 @@ $ cargo build
|
||||
|
||||
[cratesio]: https://crates.io
|
||||
|
||||
在更新完 registry 后,Cargo 检查 `[dependencies]` 段落并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 的拷贝,因为 `rand` 依赖 `libc` 来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
|
||||
在更新完 registry 后,Cargo 检查 `[dependencies]` 片段并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 的拷贝,因为 `rand` 依赖 `libc` 来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
|
||||
|
||||
如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 完成提示之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。
|
||||
|
||||
@ -308,9 +309,9 @@ $ cargo build
|
||||
|
||||
#### *Cargo.lock* 文件确保构建是可重现的
|
||||
|
||||
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖的版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `v0.3.15` 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?
|
||||
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.3.15` 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?
|
||||
|
||||
这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.3.14` 直到你显式升级,感谢 *Cargo.lock* 文件。
|
||||
这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.3.14` 直到你显式升级,感谢 *Cargo.lock* 文件。
|
||||
|
||||
#### 更新 crate 到一个新版本
|
||||
|
||||
@ -324,7 +325,7 @@ $ cargo update
|
||||
Updating rand v0.3.14 -> v0.3.15
|
||||
```
|
||||
|
||||
这时,你也会注意到的 *Cargo.lock* 文件中的变化无外乎 `rand` crate 现在使用的版本是`0.3.15`
|
||||
这时,你也会注意到的 *Cargo.lock* 文件中的变化无外乎现在使用的 `rand` crate 版本是`0.3.15`
|
||||
|
||||
如果想要使用 `0.4.0` 版本的 `rand` 或是任何 `0.4.x` 系列的版本,必须像这样更新 *Cargo.toml* 文件:
|
||||
|
||||
@ -371,11 +372,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 2-3:为了生成随机数而做的修改</span>
|
||||
<span class="caption">示例 2-3:添加生成随机数的代码</span>
|
||||
|
||||
首先,这里在顶部增加一行 `extern crate rand;` 通知 Rust 我们要使用外部依赖。这也会调用相应的 `use rand`,所以现在可以使用 `rand::` 前缀来调用 `rand` crate 中的任何内容。
|
||||
首先,这里在顶部增加一行 `extern crate rand;` 通知 Rust 我们要使用外部依赖 `rand`。这也会调用相应的 `use rand`,所以现在可以使用 `rand::` 前缀来调用 `rand` crate 中的任何内容。
|
||||
|
||||
接下来增加了另一行 `use`:`use rand::Rng`。`Rng` 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话此 trait 必须在作用域中。第十章会详细介绍 trait。
|
||||
接下来增加了另一行 `use`:`use rand::Rng`。`Rng` 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。
|
||||
|
||||
另外,中间还新增加了两行。`rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程本地,并从操作系统获取 seed。接下来,调用随机数生成器的 `gen_range` 方法。这个方法由刚才引入到作用域的 `Rng` trait 定义。`gen_range` 方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 `1` 和 `101` 来请求一个 1 和 100 之间的数。
|
||||
|
||||
@ -406,7 +407,7 @@ You guessed: 5
|
||||
|
||||
你应该能得到不同的随机数,同时它们应该都是在 1 和 100 之间的。干得漂亮!
|
||||
|
||||
## 比较猜测与秘密数字
|
||||
## 比较猜测的数字和秘密数字
|
||||
|
||||
现在有了用户输入和一个随机数,我们可以比较它们。这个步骤如示例 2-4 所示。注意这段代码还不能通过编译,我们稍后会解释。
|
||||
|
||||
@ -443,7 +444,7 @@ fn main() {
|
||||
|
||||
一个 `match` 表达式由 **分支(arms)** 构成。一个分支包含一个 **模式**(*pattern*)和表达式开头的值与分支模式相匹配时应该执行的代码。Rust 获取提供给 `match` 的值并挨个检查每个分支的模式。`match` 结构和模式是 Rust 中强大的功能,它体现了代码可能遇到的多种情形,并帮助你确保没有遗漏处理。这些功能将分别在第六章和第十八章详细介绍。
|
||||
|
||||
让我们看看使用 `match` 表达式的例子。假设用户猜了 50,这时随机生成的秘密数字是 38。比较 50 与 38 时,因为 50 比 38 要大,`cmp` 方法会返回 `Ordering::Greater`。`Ordering::Greater` 是 `match` 表达式得到的值。它检查第一个分支的模式,`Ordering::Less` 与 `Ordering::Greater`并不匹配,所以它忽略了这个分支的动作并来到下一个分支。下一个分支的模式是 `Ordering::Greater`,**正确** 匹配!这个分支关联的代码被执行,在屏幕打印出 `Too big!`。`match` 表达式就此终止,因为该场景下没有检查最后一个分支的必要。
|
||||
让我们看看使用 `match` 表达式的例子。假设用户猜了 50,这时随机生成的秘密数字是 38。比较 50 与 38 时,因为 50 比 38 要大,`cmp` 方法会返回 `Ordering::Greater`。`Ordering::Greater` 是 `match` 表达式得到的值。它检查第一个分支的模式,`Ordering::Less` 与 `Ordering::Greater`并不匹配,所以它忽略了这个分支的代码并来到下一个分支。下一个分支的模式是 `Ordering::Greater`,**正确** 匹配!这个分支关联的代码被执行,在屏幕打印出 `Too big!`。`match` 表达式就此终止,因为该场景下没有检查最后一个分支的必要。
|
||||
|
||||
然而,示例 2-4 的代码并不能编译,可以尝试一下:
|
||||
|
||||
@ -463,7 +464,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` 函数体中增加如下两行代码来实现:
|
||||
|
||||
@ -497,9 +498,9 @@ let guess: u32 = guess.trim().parse()
|
||||
.expect("Please type a number!");
|
||||
```
|
||||
|
||||
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做`guess`的变量了吗?确实如此,不过 Rust 允许 **隐藏**(*shadow*),用一个新值来隐藏 `guess` 之前的值。这个功能常用在需要转换值类型之类的场景,它允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str` 和 `guess` 之类。(第三章会介绍 shadowing 的更多细节。)
|
||||
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做 `guess` 的变量了吗?确实如此,不过 Rust 允许用一个新值来 **隐藏** (*shadow*) `guess` 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str` 和 `guess` 之类。(第三章会介绍 shadowing 的更多细节。)
|
||||
|
||||
`guess` 被绑定到 `guess.trim().parse()` 表达式。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白。`u32` 只能由数字字符转换,不过用户必须输入 <span class="keystroke">return</span> 键才能让 `read_line` 返回,然而用户按下 <span class="keystroke">return</span> 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 <span class="keystroke">5</span> 并按下 <span class="keystroke">return</span>,`guess` 看起来像这样:`5\n`。`\n` 代表 “换行”,回车键。`trim` 方法消除 `\n`,只留下`5`。
|
||||
我们将 `guess` 绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符。`u32` 只能由数字字符转换,不过用户必须输入 <span class="keystroke">return</span> 键才能让 `read_line` 返回,然而用户按下 <span class="keystroke">return</span> 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 <span class="keystroke">5</span> 并按下 <span class="keystroke">return</span>,`guess` 看起来像这样:`5\n`。`\n` 代表 “换行”,回车键。`trim` 方法消除 `\n`,只留下`5`。
|
||||
|
||||
[字符串的 `parse` 方法][parse]<!-- ignore --> 将字符串解析成数字。因为这个方法可以解析多种数字类型,因此需要告诉 Rust 具体的数字类型,这里通过 `let guess: u32` 指定。`guess` 后面的冒号(`:`)告诉 Rust 我们指定了变量的类型。Rust 有一些内建的数字类型;`u32` 是一个无符号的 32 位整型。对于不大的正整数来说,它是不错的类型,第三章还会讲到其他数字类型。另外,程序中的 `u32` 注解以及与 `secret_number` 的比较,意味着 Rust 会推断出 `secret_number` 也是 `u32` 类型。现在可以使用相同类型比较两个值了!
|
||||
|
||||
@ -522,7 +523,7 @@ You guessed: 76
|
||||
Too big!
|
||||
```
|
||||
|
||||
漂亮!即便是在猜测之前添加了空格,程序依然能判断出用户猜测了 76。多运行程序几次来检验不同类型输入的相应行为:猜一个正确的数字,猜一个过大的数字和猜一个过小的数字。
|
||||
漂亮!即便是在猜测之前添加了空格,程序依然能判断出用户猜测了 76。多运行程序几次,输入不同的数字来检验不同的行为:猜一个正确的数字,猜一个过大的数字和猜一个过小的数字。
|
||||
|
||||
现在游戏已经大体上能玩了,不过用户只能猜一次。增加一个循环来改变它吧!
|
||||
|
||||
@ -551,9 +552,9 @@ Too big!
|
||||
}
|
||||
```
|
||||
|
||||
如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保这些代码额外缩进了一层,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像没法退出啊!
|
||||
如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保 loop 循环中的代码多缩进四个空格,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像没法退出啊!
|
||||
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 “比较猜测与秘密数字” 部分提到的 `parse`:如果用户输入一个非数字答案,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 “比较猜测与秘密数字” 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -707,6 +708,6 @@ fn main() {
|
||||
|
||||
## 总结
|
||||
|
||||
此时此刻,你顺利完成了猜猜看游戏!恭喜!
|
||||
此时此刻,你顺利完成了猜猜看游戏。恭喜!
|
||||
|
||||
这是一个通过动手实践学习 Rust 新概念的项目:`let`、`match`、方法、关联函数、使用外部 crate 等等,接下来的几章,我们将会继续深入。第三章涉及到大部分编程语言都有的概念,比如变量、数据类型和函数,以及如何在 Rust 中使用它们。第四章探索所有权(ownership),这是一个 Rust 同其他语言大不相同的功能。第五章讨论结构体和方法的语法,而第六章侧重解释枚举。
|
||||
本项目通过动手实践,向你介绍了 Rust 新概念:`let`、`match`、方法、关联函数、使用外部 crate 等等,接下来的几章,你会继续深入学习这些概念。第三章介绍大部分编程语言都有的概念,比如变量、数据类型和函数,以及如何在 Rust 中使用它们。第四章探索所有权(ownership),这是一个 Rust 同其他语言大不相同的功能。第五章讨论结构体和方法的语法,而第六章侧重解释枚举。
|
||||
|
@ -2,13 +2,13 @@
|
||||
|
||||
> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-01-variables-and-mutability.md)
|
||||
> <br>
|
||||
> commit 6aad5008b69078a2fc18e6dd7e00ef395170c749
|
||||
> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
|
||||
|
||||
第二章中提到过,变量默认是 **不可变**(*immutable*)的。这是鼓励你利用 Rust 安全和简单并发的优势来编写代码的一大助力。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 拥抱不可变性的原因及方法,以及何时你不想使用不可变性。
|
||||
第二章中提到过,变量默认是不可改变的(immutable)。这是推动你以充分利用 Rust 提供的安全性和简单并发性编写代码的众多方式之一。不过,你仍然可以使用可变变量。让我们探讨一下 Rust 拥抱不可变性的原因及方法,以及何时你不想使用不可变性。
|
||||
|
||||
当变量不可变时,意味着一旦值被绑定上一个名称,你就不能改变这个值。作为说明,通过 `cargo new --bin variables` 在 *projects* 目录生成一个叫做 *variables* 的新项目。
|
||||
当变量不可变时,一旦值被绑定一个名称上,你就不能改变这个值。为了对此进行说明,使用 `cargo new --bin variables` 命令在 *projects* 目录生成一个叫做 *variables* 的新项目。
|
||||
|
||||
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并替换其代码为如下:
|
||||
接着,在新建的 *variables* 目录,打开 *src/main.rs* 并将代码替换为如下代码,这些代码还不能编译:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -21,7 +21,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
保存并使用 `cargo run` 运行程序。应该会看到一个错误信息,如下输出所示:
|
||||
保存并使用 `cargo run` 运行程序。应该会看到一条错误信息,如下输出所示:
|
||||
|
||||
```text
|
||||
error[E0384]: cannot assign twice to immutable variable `x`
|
||||
@ -34,17 +34,17 @@ error[E0384]: cannot assign twice to immutable variable `x`
|
||||
| ^^^^^ cannot assign twice to immutable variable
|
||||
```
|
||||
|
||||
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,那也不过是说程序不能安全的完成你想让它完成的工作;而 **不能** 说明你是不是一个好程序员!有经验的 Rustacean 们一样会遇到编译错误。
|
||||
这个例子展示了编译器如何帮助你找出程序中的错误。虽然编译错误令人沮丧,但那只是表示程序不能安全的完成你想让它完成的工作;并 **不能** 说明你不是一个好程序员!经验丰富的 Rustacean 们一样会遇到编译错误。
|
||||
|
||||
这些错误给出的原因是 `不能对不可变变量二次赋值`(`cannot assign twice to immutable variable x`),因为我们尝试对不可变变量 `x` 赋第二个值。
|
||||
错误信息指出错误的原因是 `不能对不可变变量 x 二次赋值`(`cannot assign twice to immutable variable x`),因为你尝试对不可变变量 `x` 赋第二个值。
|
||||
|
||||
在尝试改变预设为不可变的值的时候产生编译错误是很重要的,因为这种情况可能导致 bug:如果代码的一部分假设一个值永远也不会改变,而另一部分代码改变了它,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 难以跟踪,尤其是第二部分代码只是 **有时** 改变其值的时候。
|
||||
在尝试改变预设为不可变的值时,产生编译时错误是很重要的,因为这种情况可能导致 bug。如果一部分代码假设一个值永远也不会改变,而另一部分代码改变了这个值,第一部分代码就有可能以不可预料的方式运行。不得不承认这种 bug 的起因难以跟踪,尤其是第二部分代码只是 **有时** 会改变值。
|
||||
|
||||
Rust 编译器保证,如果声明一个值不会变,它就真的不会变。这意味着当阅读和编写代码时,不需要追踪一个值如何以及哪里可能会被改变,从而使得代码易于推导。
|
||||
|
||||
不过可变性也是非常有用的。变量只是默认不可变,可以通过在变量名之前加 `mut` 来使其可变。除了使值可以改变之外,它向读者表明了其他代码将会改变这个变量的意图。
|
||||
不过可变性也是非常有用的。变量只是默认不可变;正如在第二章所做的那样,你可以在变量名之前加 `mut` 来使其可变。除了允许改变值之外,`mut` 向读者表明了其他代码将会改变这个变量值的意图。
|
||||
|
||||
例如,改变 *src/main.rs* 并替换其代码为如下:
|
||||
例如,让我们将 *src/main.rs* 修改为如下代码:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -57,7 +57,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
当运行这个程序,出现如下:
|
||||
现在运行这个程序,出现如下内容:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -68,23 +68,23 @@ The value of x is: 5
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
通过 `mut`,允许把绑定到 `x` 的值从 `5` 改成 `6`。在一些情况下,你会想用可变变量,因为这样的代码比起只用不可变变量的实现更容易编写。
|
||||
通过 `mut`,允许把绑定到 `x` 的值从 `5` 改成 `6`。在一些情况下,你会想用可变变量,因为与只用不可变变量相比,它会让代码更容易编写。
|
||||
|
||||
除了避免 bug 外,还有很多地方需要权衡取舍。例如,使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快。对于较小的数据结构,总是创建新实例,采用更偏向函数式的风格编程,可能会使代码更易理解,为可读性而遭受性能惩罚或许值得。
|
||||
除了防止出现 bug 外,还有很多地方需要权衡取舍。例如,使用大型数据结构时,适当地使用可变变量,可能比复制和返回新分配的实例更快。对于较小的数据结构,总是创建新实例,采用更偏向函数式的编程风格,可能会使代码更易理解,为可读性而牺牲性能或许是值得的。
|
||||
|
||||
### 变量和常量的区别
|
||||
|
||||
不允许改变值的变量,可能会使你想起另一个大部分编程语言都有的概念:**常量**(*constants*)。类似于不可变变量,常量也是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
|
||||
不允许改变值的变量,可能会使你想起另一个大部分编程语言都有的概念:**常量**(*constants*)。类似于不可变变量,常量是绑定到一个名称的不允许改变的值,不过常量与变量还是有一些区别。
|
||||
|
||||
首先,不允许对常量使用 `mut`:常量不光默认不能变,它总是不能变。
|
||||
首先,不允许对常量使用 `mut`。常量不光默认不能变,它总是不能变。
|
||||
|
||||
声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,“数据类型” 中会涉及到类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
|
||||
声明常量使用 `const` 关键字而不是 `let`,并且 *必须* 注明值的类型。在下一部分,“数据类型” 中会介绍类型和类型注解,现在无需关心这些细节,记住总是标注类型即可。
|
||||
|
||||
常量可以在任何作用域声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
|
||||
常量可以在任何作用域中声明,包括全局作用域,这在一个值需要被很多部分的代码用到时很有用。
|
||||
|
||||
最后一个区别是常量只能用于常量表达式,而不能作为函数调用的结果,或任何其他只在运行时计算的值。
|
||||
最后一个区别是,常量只能被设置为常量表达式,而不能是函数调用的结果,或任何其他只能在运行时计算出的值。
|
||||
|
||||
这是一个常量声明的例子,它的名称是 `MAX_POINTS`,值是 100,000。(Rust 的常量使用下划线分隔的大写字母的命名规范):
|
||||
这是一个声明常量的例子,它的名称是 `MAX_POINTS`,值是 100,000。(Rust 常量的命名规范是使用下划线分隔的大写字母):
|
||||
|
||||
```rust
|
||||
const MAX_POINTS: u32 = 100_000;
|
||||
@ -92,11 +92,11 @@ const MAX_POINTS: u32 = 100_000;
|
||||
|
||||
在声明它的作用域之中,常量在整个程序生命周期中都有效,这使得常量可以作为多处代码使用的全局范围的值,例如一个游戏中所有玩家可以获取的最高分或者光速。
|
||||
|
||||
将用于整个程序的硬编码的值声明为常量对后来的维护者了解值的意义很有帮助。同时将硬编码的值汇总于一处,也能为将来修改提供方便。
|
||||
将遍布于应用程序中的硬编码值声明为常量,能帮助后来的代码维护人员了解值的意图。如果将来需要修改硬编码值,也只需修改汇聚于一处的硬编码值。
|
||||
|
||||
### 隐藏(Shadowing)
|
||||
|
||||
如第二章 “猜猜看游戏” 所讲的,我们可以定义一个与之前变量重名的新变量,而新变量会 **隐藏** 之前的变量。Rustacean 们称之为第一个变量被第二个 **隐藏** 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏它自己,以及重复使用 `let` 关键字来多次隐藏,如下所示:
|
||||
正如在第二章猜猜看游戏的 “比较猜测的数字和秘密数字” 中所讲,我们可以定义一个与之前变量同名的新变量,而新变量会 **隐藏** 之前的变量。Rustacean 们称之为第一个变量被第二个 **隐藏** 了,这意味着使用这个变量时会看到第二个值。可以用相同变量名称来隐藏一个变量,以及重复使用 `let` 关键字来多次隐藏,如下所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -112,7 +112,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
这个程序首先将 `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`。运行这个程序,它会有如下输出:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -122,23 +122,23 @@ $ cargo run
|
||||
The value of x is: 12
|
||||
```
|
||||
|
||||
这与将变量声明为 `mut` 是有区别的。因为除非再次使用 `let` 关键字,不小心尝试对变量重新赋值会导致编译时错误。我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。
|
||||
隐藏与将变量标记为 `mut` 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 `let` 关键字,就会导致编译时错误。通过使用 `let`,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。
|
||||
|
||||
`mut` 与隐藏的另一个区别是,当再次使用 `let` 时,实际上创建了一个新变量,我们可以改变值的类型,从而复用这个名字。例如,假设程序请求用户输入空格来提供文本间隔,然而我们真正需要的是将输入存储成数字(多少个空格):
|
||||
`mut` 与隐藏的另一个区别是,当再次使用 `let` 时,实际上创建了一个新变量,我们可以改变值的类型,但复用这个名字。例如,假设程序请求用户输入空格字符来说明希望在文本之间显示多少个空格,然而我们真正需要的是将输入存储成数字(多少个空格):
|
||||
|
||||
```rust
|
||||
let spaces = " ";
|
||||
let spaces = spaces.len();
|
||||
```
|
||||
|
||||
这里允许第一个 `spaces` 变量是字符串类型,而第二个 `spaces` 变量,它是一个恰巧与第一个变量同名的崭新变量,是数字类型。隐藏使我们不必使用不同的名字,如 `spaces_str` 和 `spaces_num`;相反,我们可以复用 `spaces` 这个更简单的名字。然而,如果尝试使用 `mut`,如下所示:
|
||||
这里允许第一个 `spaces` 变量是字符串类型,而第二个 `spaces` 变量,它是一个恰巧与第一个变量同名的崭新变量,是数字类型。隐藏使我们不必使用不同的名字,如 `spaces_str` 和 `spaces_num`;相反,我们可以复用 `spaces` 这个更简单的名字。然而,如果尝试使用 `mut`,将会得到一个编译时错误,如下所示:
|
||||
|
||||
```rust,ignore
|
||||
let mut spaces = " ";
|
||||
spaces = spaces.len();
|
||||
```
|
||||
|
||||
会导致一个编译错误,因为改变一个变量的类型是不被允许的:
|
||||
这个错误说明,我们不能改变变量的类型:
|
||||
|
||||
```text
|
||||
error[E0308]: mismatched types
|
||||
@ -151,4 +151,4 @@ error[E0308]: mismatched types
|
||||
found type `usize`
|
||||
```
|
||||
|
||||
现在我们已经了解了变量如何工作,让我们再看看更多变量可以拥有的数据类型。
|
||||
现在我们已经了解了变量如何工作,让我们看看变量可以拥有的更多数据类型。
|
||||
|
@ -2,17 +2,17 @@
|
||||
|
||||
> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-02-data-types.md)
|
||||
> <br>
|
||||
> commit ec65990849230388e4ce4db5b7a0cb8a0f0d60e2
|
||||
> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
|
||||
|
||||
在 Rust 中,任何值都属于一种明确的 **类型**(*type*),这告诉了 Rust 它被指定为何种数据,以便明确其处理方式。本部分我们将看到一系列内建于语言中的类型。我们将其分为两类:标量(scalar)和复合(compound)。
|
||||
在 Rust 中,每一个值都属于某一个 **数据类型**(*data type*),这告诉 Rust 它被指定为何种数据,以便明确数据处理方式。我们将看到两类数据类型:标量(scalar)和复合(compound)。
|
||||
|
||||
Rust 是 **静态类型**(*statically typed*)语言,也就是说在编译时就必须知道所有变量的类型,这一点将贯穿整个章节。通过值的形式及其使用方式,编译器通常可以推断出我们想要用的类型。多种类型均有可能时,比如第二章中使用 `parse` 将 `String` 转换为数字时,必须增加类型注解,像这样:
|
||||
记住,Rust 是 **静态类型**(*statically typed*)语言,也就是说在编译时就必须知道所有变量的类型。根据值及其使用方式,编译器通常可以推断出我们想要用的类型。当多种类型均有可能时,比如第二章的 “比较猜测的数字和秘密数字” 使用 `parse` 将 `String` 转换为数字时,必须增加类型注解,像这样:
|
||||
|
||||
```rust
|
||||
let guess: u32 = "42".parse().expect("Not a number!");
|
||||
```
|
||||
|
||||
这里如果不添加类型注解,Rust 会显示如下错误,这说明编译器需要更多信息,来了解我们想要的类型:
|
||||
这里如果不添加类型注解,Rust 会显示如下错误,这说明编译器需要我们提供更多信息,来了解我们想要的类型:
|
||||
|
||||
```text
|
||||
error[E0282]: type annotations needed
|
||||
@ -25,33 +25,33 @@ error[E0282]: type annotations needed
|
||||
| consider giving `guess` a type
|
||||
```
|
||||
|
||||
在我们讨论各种数据类型时,你会看到不同的类型注解。
|
||||
你会看到其它数据类型的各种类型注解。
|
||||
|
||||
### 标量类型
|
||||
|
||||
**标量**(*scalar*)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们,不过让我们深入了解它们在 Rust 中是如何工作的。
|
||||
**标量**(*scalar*)类型代表一个单独的值。Rust 有四种基本的标量类型:整型、浮点型、布尔类型和字符类型。你可能在其他语言中见过它们。让我们深入了解它们在 Rust 中是如何工作的。
|
||||
|
||||
#### 整型
|
||||
|
||||
**整数** 是一个没有小数部分的数字。我们在这一章的前面使用过 `u32` 类型。该类型声明表明,u32 关联的值应该是一个占据 32 比特位的无符号整数(有符号整型类型以 `i` 开头而不是 `u`)。表格 3-1 展示了 Rust 内建的整数类型。每一种变体都有符号和无符号列(例如,*i8*)可以用来声明对应的整数值。
|
||||
**整数** 是一个没有小数部分的数字。我们在第二章使用过 `u32` 整数类型。该类型声明表明,它关联的值应该是一个占据 32 比特位的无符号整数(有符号整数类型以 `i` 开头而不是 `u`)。表格 3-1 展示了 Rust 内建的整数类型。在有符号列和无符号列中的每一个变体(例如,`i16`)都可以用来声明整数值的类型。
|
||||
|
||||
<span class="caption">表格 3-1: Rust 中的整型</span>
|
||||
|
||||
| 长度 | 有符号 | 无符号 |
|
||||
|--------|--------|----------|
|
||||
| 8-bit | i8 | u8 |
|
||||
| 16-bit | i16 | u16 |
|
||||
| 32-bit | i32 | u32 |
|
||||
| 64-bit | i64 | u64 |
|
||||
| arch | isize | usize |
|
||||
|--------|---------|----------|
|
||||
| 8-bit | `i8` | `u8` |
|
||||
| 16-bit | `i16` | `u16` |
|
||||
| 32-bit | `i32` | `u32` |
|
||||
| 64-bit | `i64` | `u64` |
|
||||
| arch | `isize` | `usize` |
|
||||
|
||||
每一种变体都可以是有符号或无符号的,并有一个明确的大小。有符号和无符号代表数字能否为负值;换句话说,数字是否需要有一个符号(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以二进制补码形式(two’s complement representation)存储(如果你不清楚这是什么,可以在网上搜索;对其的解释超出了本书的范畴)。
|
||||
每一个变体都可以是有符号或无符号的,并有一个明确的大小。**有符号** 和 **无符号** 代表数字能否为负值,换句话说,数字是否需要有一个符号(有符号数),或者永远为正而不需要符号(无符号数)。这有点像在纸上书写数字:当需要考虑符号的时候,数字以加号或减号作为前缀;然而,可以安全地假设为正数时,加号前缀通常省略。有符号数以补码形式(two’s complement representation)存储(如果你不清楚这是什么,可以在网上搜索;对其的解释超出了本书的范畴)。
|
||||
|
||||
每一个有符号的变体可以储存包含从 -(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。
|
||||
每一个有符号的变体可以储存包含从 -(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 中的任何一种形式编写数字字面值。注意除 byte 以外的所有数字字面值允许使用类型后缀,例如 `57u8`,同时也允许使用 `_` 做为分隔符以方便读数,例如`1_000`。
|
||||
|
||||
<span class="caption">表格 3-2: Rust 中的整型字面值</span>
|
||||
|
||||
@ -67,7 +67,7 @@ error[E0282]: type annotations needed
|
||||
|
||||
#### 浮点型
|
||||
|
||||
Rust 同样有两个主要的 **浮点数**(*floating-point numbers*)类型,`f32` 和 `f64`,它们是带小数点的数字,分别占 32 位和 64 位比特。默认类型是 `f64`,因为在现代 CPU 中它与 `f32` 速度几乎一样,不过精度更高。
|
||||
Rust 也有两个原生的 **浮点数**(*floating-point numbers*)类型,它们是带小数点的数字。Rust 的浮点数类型是 `f32` 和 `f64`,分别占 32 位和 64 位。默认类型是 `f64`,因为在现代 CPU 中,它与 `f32` 速度几乎一样,不过精度更高。
|
||||
|
||||
这是一个展示浮点数的实例:
|
||||
|
||||
@ -83,32 +83,32 @@ fn main() {
|
||||
|
||||
浮点数采用 IEEE-754 标准表示。`f32` 是单精度浮点数,`f64` 是双精度浮点数。
|
||||
|
||||
#### 数字运算符
|
||||
#### 数值运算
|
||||
|
||||
Rust 支持所有数字类型常见的基本数学运算操作:加法、减法、乘法、除法以及取余。下面的代码展示了如何在一个 `let` 语句中使用它们:
|
||||
Rust 中的所有数字类型都支持基本数学运算:加法、减法、乘法、除法和取余。下面的代码展示了如何在 `let` 语句中使用它们:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// addition
|
||||
// 加法
|
||||
let sum = 5 + 10;
|
||||
|
||||
// subtraction
|
||||
// 减法
|
||||
let difference = 95.5 - 4.3;
|
||||
|
||||
// multiplication
|
||||
// 乘法
|
||||
let product = 4 * 30;
|
||||
|
||||
// division
|
||||
// 除法
|
||||
let quotient = 56.7 / 32.2;
|
||||
|
||||
// remainder
|
||||
// 取余
|
||||
let remainder = 43 % 5;
|
||||
}
|
||||
```
|
||||
|
||||
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,它们绑定到了一个变量。附录 B 包含了一个 Rust 提供的所有运算符的列表。
|
||||
这些语句中的每个表达式使用了一个数学运算符并计算出了一个值,然后绑定给一个变量。附录 B 包含 Rust 提供的所有运算符的列表。
|
||||
|
||||
#### 布尔型
|
||||
|
||||
@ -120,15 +120,15 @@ fn main() {
|
||||
fn main() {
|
||||
let t = true;
|
||||
|
||||
let f: bool = false; // with explicit type annotation
|
||||
let f: bool = false; // 显式指定类型注解
|
||||
}
|
||||
```
|
||||
|
||||
使用布尔值的主要场景是条件表达式,例如 `if` 表达式。在 “控制流”(“Control Flow”)部分将讲到 `if` 表达式在 Rust 中如何工作。
|
||||
使用布尔值的主要场景是条件表达式,例如 `if` 表达式。在 “控制流”(“Control Flow”)部分将介绍 `if` 表达式在 Rust 中如何工作。
|
||||
|
||||
#### 字符类型
|
||||
|
||||
目前为止只使用到了数字,不过 Rust 也支持字符。Rust 的 `char` 类型是大部分语言中基本字母字符类型,如下代码展示了如何使用它。注意 `char` 由单引号指定,不同于字符串使用双引号:
|
||||
目前为止只使用到了数字,不过 Rust 也支持字母。Rust 的 `char` 类型是语言中最原生的字母类型,如下代码展示了如何使用它。(注意 `char` 由单引号指定,不同于字符串使用双引号。)
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -140,17 +140,17 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
Rust 的 `char` 类型代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。拼音字母(Accented letters),中文/日文/韩文等象形文字,emoji(絵文字)以及零长度的空白字符对于 Rust `char` 类型都是有效的。Unicode 标量值包含从 `U+0000` 到 `U+D7FF` 和 `U+E000` 到 `U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 “字符串” 部分将详细讨论这个主题。
|
||||
Rust 的 `char` 类型代表了一个 Unicode 标量值(Unicode Scalar Value),这意味着它可以比 ASCII 表示更多内容。在 Rust 中,拼音字母(Accented letters),中文、日文、韩文等字符,emoji(绘文字)以及零长度的空白字符都是有效的 `char` 值。Unicode 标量值包含从 `U+0000` 到 `U+D7FF` 和 `U+E000` 到 `U+10FFFF` 在内的值。不过,“字符” 并不是一个 Unicode 中的概念,所以人直觉上的 “字符” 可能与 Rust 中的 `char` 并不符合。第八章的 “字符串” 中将详细讨论这个主题。
|
||||
|
||||
### 复合类型
|
||||
|
||||
**复合类型**(*Compound types*)可以将多个其他类型的值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
|
||||
**复合类型**(*Compound types*)可以将多个值组合成一个类型。Rust 有两个原生的复合类型:元组(tuple)和数组(array)。
|
||||
|
||||
#### 将值组合进元组
|
||||
#### 元组类型
|
||||
|
||||
元组是一个将多个其他类型的值组合进一个复合类型的主要方式。
|
||||
|
||||
我们使用一个括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了额外的可选类型注解:
|
||||
我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。这个例子中使用了可选的类型注解:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -160,7 +160,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
`tup` 变量绑定了整个元组,因为元组被认为是一个单独的复合元素。为了从元组中获取单个的值,可以使用模式匹配(pattern matching)来解构(destructure)元组,像这样:
|
||||
`tup` 变量绑定到整个元组上,因为元组是一个单独的复合元素。为了从元组中获取单个值,可以使用模式匹配(pattern matching)来解构(destructure)元组值,像这样:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -176,7 +176,7 @@ fn main() {
|
||||
|
||||
程序首先创建了一个元组并绑定到 `tup` 变量上。接着使用了 `let` 和一个模式将 `tup` 分成了三个不同的变量,`x`、`y` 和 `z`。这叫做 **解构**(*destructuring*),因为它将一个元组拆成了三个部分。最后,程序打印出了 `y` 的值,也就是 `6.4`。
|
||||
|
||||
除了使用模式匹配解构之外,也可以使用点号(`.`)后跟值的索引来直接访问它们。例如:
|
||||
除了使用模式匹配解构外,也可以使用点号(`.`)后跟值的索引来直接访问它们。例如:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -194,11 +194,11 @@ fn main() {
|
||||
|
||||
这个程序创建了一个元组,`x`,并接着使用索引为每个元素创建新变量。跟大多数编程语言一样,元组的第一个索引值是 0。
|
||||
|
||||
#### 数组
|
||||
#### 数组类型
|
||||
|
||||
另一个获取一个多个值集合的方式是 **数组**(*array*)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
|
||||
另一个包含多个值的方式是 **数组**(*array*)。与元组不同,数组中的每个元素的类型必须相同。Rust 中的数组与一些其他语言中的数组不同,因为 Rust 中的数组是固定长度的:一旦声明,它们的长度不能增长或缩小。
|
||||
|
||||
Rust 中数组的值位于中括号中的逗号分隔的列表中:
|
||||
Rust 中,数组中的值位于中括号内的逗号分隔的列表中:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -208,9 +208,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用,虽然它并不如 vector 类型那么灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。第八章会详细讨论 vector。
|
||||
当你想要在栈(stack)而不是在堆(heap)上为数据分配空间(第四章将讨论栈与堆的更多内容),或者是想要确保总是有固定数量的元素时,数组非常有用。但是数组并不如 vector 类型灵活。vector 类型是标准库提供的一个 **允许** 增长和缩小长度的类似数组的集合类型。当不确定是应该使用数组还是 vector 的时候,你可能应该使用 vector。第八章会详细讨论 vector。
|
||||
|
||||
一个你可能想要使用数组而不是 vector 的例子是,当程序需要知道一年中月份的名字时,程序不大可能会去增加或减少月份。这时你可以使用数组,因为我们知道它总是含有 12 个元素:
|
||||
一个你可能想要使用数组而不是 vector 的例子是,当程序需要知道一年中月份的名字时。程序不大可能会去增加或减少月份。这时你可以使用数组,因为我们知道它总是包含 12 个元素:
|
||||
|
||||
```rust
|
||||
let months = ["January", "February", "March", "April", "May", "June", "July",
|
||||
@ -236,7 +236,7 @@ fn main() {
|
||||
|
||||
##### 无效的数组元素访问
|
||||
|
||||
如果我们访问数组结尾之后的元素会发生什么呢?比如我们将上面的例子改成下面这样,这可以编译不过在运行时会因错误而退出:
|
||||
如果我们访问数组结尾之后的元素会发生什么呢?比如你将上面的例子改成下面这样,这可以编译不过在运行时会因错误而退出:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -263,6 +263,6 @@ thread '<main>' panicked at 'index out of bounds: the len is 5 but the index is
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
```
|
||||
|
||||
编译并没有产生任何错误,不过程序会导致一个 **运行时**(*runtime*)错误并且不会成功退出。当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 *panic*,这是 Rust 中的术语,它用于程序因为错误而退出的情况。
|
||||
编译并没有产生任何错误,不过程序会出现一个 **运行时**(*runtime*)错误并且不会成功退出。当尝试用索引访问一个元素时,Rust 会检查指定的索引是否小于数组的长度。如果索引超出了数组长度,Rust 会 *panic*,这是 Rust 术语,它用于程序因为错误而退出的情况。
|
||||
|
||||
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中,并没有进行这类检查,这样当提供了一个不正确的索引时,就会访问无效的内存。Rust 通过立即退出而不是允许内存访问并继续执行来使你免受这类错误困扰。第九章会讨论更多 Rust 的错误处理。
|
||||
这是第一个在实战中遇到的 Rust 安全原则的例子。在很多底层语言中,并没有进行这类检查,这样当提供了一个不正确的索引时,就会访问无效的内存。通过立即退出而不是允许内存访问并继续执行,Rust 让你避开此类错误。第九章会讨论更多 Rust 的错误处理。
|
||||
|
@ -1,12 +1,12 @@
|
||||
## 函数如何工作
|
||||
## 函数
|
||||
|
||||
> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-03-how-functions-work.md)
|
||||
> <br>
|
||||
> commit 6aad5008b69078a2fc18e6dd7e00ef395170c749
|
||||
> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
|
||||
|
||||
函数在 Rust 代码中应用广泛。你已经见过一个语言中最重要的函数:`main` 函数,它是很多程序的入口点。你也见过了 `fn` 关键字,它用来声明新函数。
|
||||
函数遍布于 Rust 代码中。你已经见过语言中最重要的函数之一:`main` 函数,它是很多程序的入口点。你也见过 `fn` 关键字,它用来声明新函数。
|
||||
|
||||
Rust 代码使用 *snake case* 作为函数和变量名称的规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这里是一个包含函数定义的程序的例子:
|
||||
Rust 代码中的函数和变量名使用 *snake case* 规范风格。在 snake case 中,所有字母都是小写并使用下划线分隔单词。这是一个包含函数定义示例的程序:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -22,11 +22,11 @@ fn another_function() {
|
||||
}
|
||||
```
|
||||
|
||||
Rust 中的函数定义以 `fn` 开始并在函数名后跟一对括号。大括号告诉编译器哪里是函数体的开始和结尾。
|
||||
Rust 中的函数定义以 `fn` 开始并在函数名后跟一对圆括号。大括号告诉编译器哪里是函数体的开始和结尾。
|
||||
|
||||
可以使用定义过的函数名后跟括号来调用任意函数。因为 `another_function` 已经在程序中定义过了,它可以在 `main` 函数中被调用。注意,源码中 `another_function` 在 `main` 函数 **之后** 被定义;也可以在其之前定义。Rust 不关心函数定义于何处,只要它们被定义了。
|
||||
可以使用函数名后跟圆括号来调用我们定义过的任意函数。因为程序中已定义 `another_function` 函数,所以可以在 `main` 函数中调用它。注意,源码中 `another_function` 定义在 `main` 函数 **之后**;也可以定义在之前。Rust 不关心函数定义于何处,只要定义了就行。
|
||||
|
||||
让我们开始一个叫做 *functions* 的新二进制项目来进一步探索函数。将上面的 `another_function` 例子写入 *src/main.rs* 中并运行。你应该会看到如下输出:
|
||||
让我们新建一个叫做 *functions* 的二进制项目来进一步探索函数。将上面的 `another_function` 例子写入 *src/main.rs* 中并运行。你应该会看到如下输出:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -37,13 +37,13 @@ Hello, world!
|
||||
Another function.
|
||||
```
|
||||
|
||||
代码在 `main` 函数中按照它们出现的顺序被执行。首先,打印 “Hello, world!” 信息,接着 `another_function` 被调用并打印它的信息。
|
||||
`main` 函数中的代码会按顺序执行。首先,打印 “Hello, world!” 信息,然后调用 `another_function` 函数并打印它的信息。
|
||||
|
||||
### 函数参数
|
||||
|
||||
函数也可以被定义为拥有 **参数**(*parameters*),它们是作为函数签名一部分的特殊变量。当函数拥有参数时,可以为这些参数提供具体的值。技术上讲,这些具体值被称为参数(*arguments*),不过通常的习惯是倾向于在函数定义中的变量和调用函数时传递的具体值都可以用 “parameter” 和 “argument” 而不加区别。
|
||||
函数也可以被定义为拥有 **参数**(*parameters*),参数是特殊变量,是函数签名的一部分。当函数拥有参数(形参)时,可以为这些参数提供具体的值(实参)。技术上讲,这些具体值被称为参数(*arguments*),但是在闲谈时,人们倾向于互换着使用 *parameter* 和 *argument* 来表示函数定义中的变量或调用函数时传入的具体值。
|
||||
|
||||
如下被重写的 `another_function` 版本展示了 Rust 中参数是什么样的:
|
||||
下面被重写的 `another_function` 版本展示了 Rust 中参数是什么样的:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -57,7 +57,7 @@ fn another_function(x: i32) {
|
||||
}
|
||||
```
|
||||
|
||||
尝试运行程序,将会得到如下输出:
|
||||
尝试运行程序,将会输出如下内容:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -67,11 +67,11 @@ $ cargo run
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
`another_function` 的声明有一个叫做 `x` 的参数。`x` 的类型被指定为 `i32`。当 `5` 被传递给 `another_function` 时,`println!` 宏将 `5` 放入格式化字符串中大括号的位置。
|
||||
`another_function` 的声明中有一个命名为 `x` 的参数。`x` 的类型被指定为 `i32`。当将 `5` 传给 `another_function` 时,`println!` 宏将 `5` 放入格式化字符串中大括号的位置。
|
||||
|
||||
在函数签名中 **必须** 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解意味着编译器不需要在别的地方要求你注明类型就能知道你的意图。
|
||||
在函数签名中,**必须** 声明每个参数的类型。这是 Rust 设计中一个经过慎重考虑的决定:要求在函数定义中提供类型注解,意味着编译器不需要你在代码的其他地方注明类型来指出你的意图。
|
||||
|
||||
当一个函数有多个参数时,使用逗号隔开它们,像这样:
|
||||
当一个函数有多个参数时,使用逗号分隔,像这样:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -86,7 +86,7 @@ fn another_function(x: i32, y: i32) {
|
||||
}
|
||||
```
|
||||
|
||||
这个例子创建了一个有两个参数的函数,都是 `i32` 类型的。函数打印出了这两个参数的值。注意函数参数并不一定都是相同类型的,这个例子中它们只是碰巧相同罢了。
|
||||
这个例子创建了有两个参数的函数,都是 `i32` 类型。函数打印出了这两个参数的值。注意函数的参数类型并不一定相同,这个例子中只是碰巧相同罢了。
|
||||
|
||||
尝试运行代码。使用上面的例子替换当前 *functions* 项目的 *src/main.rs* 文件,并用 `cargo run` 运行它:
|
||||
|
||||
@ -99,17 +99,15 @@ The value of x is: 5
|
||||
The value of y is: 6
|
||||
```
|
||||
|
||||
因为我们使用 `5` 作为 `x` 的值 `6` 作为 `y` 的值来调用函数,这两个字符串和它们的值被相应打印出来。
|
||||
因为我们使用 `5` 作为 `x` 的值,`6` 作为 `y` 的值来调用函数,因此打印出这两个字符串及相应的值。
|
||||
|
||||
### 函数体
|
||||
### 包含语句和表达式的函数体
|
||||
|
||||
函数体由一系列的语句和一个可选的表达式构成。目前为止,我们只涉及到了没有结尾表达式的函数,不过我们见过表达式作为了语句的一部分。因为 Rust 是一个基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别。其他语言并没有这样的区别,所以让我们看看语句与表达式有什么区别以及它们是如何影响函数体的。
|
||||
函数体由一系列的语句和一个可选的结尾表达式构成。目前为止,我们只介绍了没有结尾表达式的函数,不过你已经见过作为语句一部分的表达式。因为 Rust 是一门基于表达式(expression-based)的语言,这是一个需要理解的(不同于其他语言)重要区别。其他语言并没有这样的区别,所以让我们看看语句与表达式有什么区别以及这些区别是如何影响函数体的。
|
||||
|
||||
### 语句与表达式
|
||||
实际上,我们已经使用过语句和表达式。**语句**(*Statements*)是执行一些操作但不返回值的指令。表达式(*Expressions*)计算并产生一个值。让我们看一些例子:
|
||||
|
||||
我们已经用过语句与表达式了。**语句**(*Statements*)是执行一些操作但不返回值的指令。表达式(*Expressions*)计算并产生一个值。让我们看一些例子:
|
||||
|
||||
使用 `let` 关键字创建变量并绑定一个值是一个语句。在列表 3-1 中,`let y = 6;` 是一个语句:
|
||||
使用 `let` 关键字创建变量并绑定一个值是一个语句。在列表 3-1 中,`let y = 6;` 是一个语句。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -123,7 +121,7 @@ fn main() {
|
||||
|
||||
函数定义也是语句,上面整个例子本身就是一个语句。
|
||||
|
||||
语句并不返回值。因此,不能把 `let` 语句赋值给另一个变量,比如下面的例子尝试做的,这会产生一个错误:
|
||||
语句不返回值。因此,不能把 `let` 语句赋值给另一个变量,比如下面的例子尝试做的,会产生一个错误:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -133,7 +131,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
当运行这个程序,会得到如下错误:
|
||||
当运行这个程序时,会得到如下错误:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -147,9 +145,9 @@ error: expected expression, found statement (`let`)
|
||||
= note: variable declaration using `let` is a statement
|
||||
```
|
||||
|
||||
`let y = 6` 语句并不返回值,所以并没有 `x` 可以绑定的值。这与其他语言不同,例如 C 和 Ruby,它们的赋值语句返回所赋的值。在这些语言中,可以这么写 `x = y = 6` 这样 `x` 和 `y` 的值都是 `6`;这在 Rust 中可不行。
|
||||
`let y = 6` 语句并不返回值,所以没有可以绑定到 `x` 上的值。这与其他语言不同,例如 C 和 Ruby,它们的赋值语句会返回所赋的值。在这些语言中,可以这么写 `x = y = 6`,这样 `x` 和 `y` 的值都是 `6`;Rust 中不能这样写。
|
||||
|
||||
表达式计算出一些值,而且它们组成了其余大部分你将会编写的 Rust 代码。考虑一个简单的数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在列表 3-3 中有这个语句 `let y = 6;`,`6` 是一个表达式,它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),`{}`,也是一个表达式,例如:
|
||||
表达式会计算出一些值,并且你将编写的大部分 Rust 代码是由表达式组成的。考虑一个简单的数学运算,比如 `5 + 6`,这是一个表达式并计算出值 `11`。表达式可以是语句的一部分:在示例 3-1 中,语句 `let y = 6;` 中的 `6` 是一个表达式,它计算出的值是 `6`。函数调用是一个表达式。宏调用是一个表达式。我们用来创建新作用域的大括号(代码块),`{}`,也是一个表达式,例如:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -175,11 +173,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
是一个代码块,它的值是 `4`。这个值作为 `let` 语句的一部分被绑定到 `y` 上。注意结尾没有分号的那一行,与大部分我们见过的代码行不同。表达式并不包含结尾的分号。如果在表达式的结尾加上分号,他就变成了语句,这也就使其不返回一个值。在接下来的探索中记住函数和表达式都返回值就行了。
|
||||
是一个代码块,它的值是 `4`。这个值作为 `let` 语句的一部分被绑定到 `y` 上。注意结尾没有分号的那一行 `x+1`,与你见过的大部分代码行不同。表达式的结尾没有分号。如果在表达式的结尾加上分号,它就变成了语句,而语句不会返回值。在接下来探索具有返回值的函数和表达式时要谨记这一点。
|
||||
|
||||
### 函数的返回值
|
||||
### 具有返回值的函数
|
||||
|
||||
函数可以向调用它的代码返回值。我们并不对返回值命名,不过会在一个箭头(`->`)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。这是一个有返回值的函数的例子:
|
||||
函数可以向调用它的代码返回值。我们并不对返回值命名,但要在箭头(`->`)后声明它的类型。在 Rust 中,函数的返回值等同于函数体最后一个表达式的值。使用 `return` 关键字和指定值,可从函数中提前返回;但大部分函数隐式的返回最后的表达式。这是一个有返回值的函数的例子:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -195,7 +193,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
在函数 `five` 中并没有函数调用、宏、甚至也没有 `let` 语句————只有数字 `5` 自身。这在 Rust 中是一个完全有效的函数。注意函数的返回值类型也被指定了,就是 `-> i32`。尝试运行代码;输出应该看起来像这样:
|
||||
在 `five` 函数中没有函数调用、宏、甚至没有 `let` 语句——只有数字 `5`。这在 Rust 中是一个完全有效的函数。注意,也指定了函数返回值的类型,就是 `-> i32`。尝试运行代码;输出应该看起来像这样:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -205,13 +203,15 @@ $ cargo run
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
函数 `five` 的返回值是 `5`,也就是为什么返回值类型是 `i32`。让我们仔细检查一下这段代码。这有两个重要的部分:首先,`let x = five();` 这一行表明我们使用函数的返回值来初始化了一个变量。因为函数 `five` 返回 `5`,这一行与如下这行相同:
|
||||
`five` 函数的返回值是 `5`,所以返回值类型是 `i32`。让我们仔细检查一下这段代码。有两个重要的部分:首先,`let x = five();` 这一行表明我们使用函数的返回值初始化一个变量。因为 `five` 函数返回 `5`,这一行与如下代码相同:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
```
|
||||
|
||||
其次,函数 `five` 没有参数并定义了返回值类型,不过函数体只有单单一个 `5` 也没有分号,因为这是我们想要返回值的表达式。让我们看看另一个例子:
|
||||
其次,`five` 函数没有参数并定义了返回值类型,不过函数体只有单单一个 `5` 也没有分号,因为这是一个表达式,我们想要返回它的值。
|
||||
|
||||
让我们看看另一个例子:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -227,7 +227,7 @@ fn plus_one(x: i32) -> i32 {
|
||||
}
|
||||
```
|
||||
|
||||
运行代码会打印出 `The value of x is: 6`。如果在包含 `x + 1` 的那一行的结尾加上一个分号,把它从表达式变成语句后会怎样呢?
|
||||
运行代码会打印出 `The value of x is: 6`。但如果在包含 `x + 1` 的行尾加上一个分号,把它从表达式变成语句,我们将看到一个错误。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -260,4 +260,4 @@ error[E0308]: mismatched types
|
||||
found type `()`
|
||||
```
|
||||
|
||||
主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 `plus_one` 的定义说明它要返回一个 `i32`,不过语句并不返回一个值,这由那个空元组 `()` 表明。因此,这个函数返回了空元组 `()`,这与函数定义相矛盾并导致一个错误。在输出中,Rust 提供了一个可能会对修正问题有帮助的信息:它建议去掉分号,这会修复这个错误。
|
||||
主要的错误信息,“mismatched types”(类型不匹配),揭示了代码的核心问题。函数 `plus_one` 的定义说明它要返回一个 `i32` 类型的值,不过语句并不会返回值,使用空元组 `()` 表示不返回值。因为不返回值与函数定义相矛盾,从而出现一个错误。在输出中,Rust 提供了一条信息,可能有助于纠正这个错误:它建议删除分号,这会修复这个错误。
|
||||
|
@ -2,17 +2,17 @@
|
||||
|
||||
> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-04-comments.md)
|
||||
> <br>
|
||||
> commit d4c77666f480edfb960cc9b11a31c42f4b90c745
|
||||
> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
|
||||
|
||||
所有编程语言都力求使其代码易于理解,不过有时需要提供额外的解释。在这种情况下,程序员在源码中留下记录,或者 **注释**(*comments*),编译器会忽略它们不过其他阅读代码的人可能会用得上。
|
||||
所有程序员都力求使其代码易于理解,不过有时还需要提供额外的解释。在这种情况下,程序员在源码中留下记录,或者 **注释**(*comments*),编译器会忽略它们,不过阅读代码的人可能觉得有用。
|
||||
|
||||
这是一个简单的注释的例子:
|
||||
这是一个简单的注释:
|
||||
|
||||
```rust
|
||||
// Hello, world.
|
||||
// hello, world
|
||||
```
|
||||
|
||||
在 Rust 中,注释必须以两道斜杠开始,并持续到本行的结尾。对于超过一行的注释,需要在每一行都加上 `//`,像这样:
|
||||
在 Rust 中,注释必须以两道斜杠开始,并持续到本行的结尾。对于超过一行的注释,需要在每一行前都加上 `//`,像这样:
|
||||
|
||||
```rust
|
||||
// So we’re doing something complicated here, long enough that we need
|
||||
@ -26,17 +26,17 @@
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let lucky_number = 7; // I’m feeling lucky today.
|
||||
let lucky_number = 7; // I’m feeling lucky today
|
||||
}
|
||||
```
|
||||
|
||||
不过你会经常看到它们用于这种格式,也就是位于它所解释的代码行的上面一行:
|
||||
不过你更经常看到以这种格式使用它们,也就是位于它所解释的代码行的上面一行:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// I’m feeling lucky today.
|
||||
// I’m feeling lucky today
|
||||
let lucky_number = 7;
|
||||
}
|
||||
```
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
> [ch03-05-control-flow.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch03-05-control-flow.md)
|
||||
> <br>
|
||||
> commit ec65990849230388e4ce4db5b7a0cb8a0f0d60e2
|
||||
> commit f949ff883628db8ed2f2f5f19e146ebf19ed6a6f
|
||||
|
||||
通过条件是不是为真来决定是否执行某些代码,或者根据条件是否为真来重复运行一段代码是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
|
||||
根据条件是否为真来决定是否执行某些代码,以及根据条件是否为真来重复运行一段代码是大部分编程语言的基本组成部分。Rust 代码中最常见的用来控制执行流的结构是 `if` 表达式和循环。
|
||||
|
||||
### `if` 表达式
|
||||
|
||||
`if` 表达式允许根据条件执行不同的代码分支。我们提供一个条件并表示 “如果符合这个条件,运行这段代码。如果条件不满足,不运行这段代码。”
|
||||
`if` 表达式允许根据条件执行不同的代码分支。你提供一个条件并表示 “如果条件满足,运行这段代码;如果条件不满足,不运行这段代码。”
|
||||
|
||||
在 *projects* 目录创建一个叫做 *branches* 的新项目来学习 `if` 表达式。在 *src/main.rs* 文件中,输入如下内容:
|
||||
在 *projects* 目录新建一个叫做 *branches* 的项目,来学习 `if` 表达式。在 *src/main.rs* 文件中,输入如下内容:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -28,7 +28,9 @@ fn main() {
|
||||
|
||||
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
|
||||
|
||||
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 是否有一个小于 5 的值。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第二章 “比较猜测与秘密数字” 部分中讨论到的 `match` 表达式中分支一样。也可以包含一个可选的 `else` 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为假时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
|
||||
所有的 `if` 表达式都以 `if` 关键字开头,其后跟一个条件。在这个例子中,条件检查变量 `number` 的值是否小于 5。在条件为真时希望执行的代码块位于紧跟条件之后的大括号中。`if` 表达式中与条件关联的代码块有时被叫做 *arms*,就像第二章 “比较猜测的数字和秘密数字” 部分中讨论到的 `match` 表达式中的分支一样。
|
||||
|
||||
也可以包含一个可选的 `else` 表达式来提供一个在条件为假时应当执行的代码块,这里我们就这么做了。如果不提供 `else` 表达式并且条件为假时,程序会直接忽略 `if` 代码块并继续执行下面的代码。
|
||||
|
||||
尝试运行代码,应该能看到如下输出:
|
||||
|
||||
@ -56,7 +58,7 @@ $ cargo run
|
||||
condition was false
|
||||
```
|
||||
|
||||
另外值得注意的是代码中的条件 **必须** 是 `bool` 值。如果想看看条件不是 `bool` 值时会发生什么,尝试运行如下代码:
|
||||
另外值得注意的是代码中的条件 **必须** 是 `bool` 值。如果条件不是 `bool` 值,我们将得到一个错误。例如,尝试运行以下代码:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -83,7 +85,7 @@ error[E0308]: mismatched types
|
||||
found type `{integer}`
|
||||
```
|
||||
|
||||
这个错误表明 Rust 期望一个 `bool` 不过却得到了一个整型。不像 Ruby 或 JavaScript 这样的语言,Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 `if` 的条件。例如,如果想要 `if` 代码块只在一个数字不等于 `0` 时执行,可以把 `if` 表达式修改成下面这样:
|
||||
这个错误表明 Rust 期望一个 `bool` 却得到了一个整数。不像 Ruby 或 JavaScript 这样的语言,Rust 并不会尝试自动地将非布尔值转换为布尔值。必须总是显式地使用布尔值作为 `if` 的条件。例如,如果想要 `if` 代码块只在一个数字不等于 `0` 时执行,可以把 `if` 表达式修改成下面这样:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -99,7 +101,7 @@ fn main() {
|
||||
|
||||
运行代码会打印出 `number was something other than zero`。
|
||||
|
||||
#### 使用 `else if` 实现多重条件
|
||||
#### 使用 `else if` 处理多重条件
|
||||
|
||||
可以将 `else if` 表达式与 `if` 和 `else` 组合来实现多重条件。例如:
|
||||
|
||||
@ -131,9 +133,9 @@ $ cargo run
|
||||
number is divisible by 3
|
||||
```
|
||||
|
||||
当执行这个程序,它按顺序检查每个 `if` 表达式并执行第一个条件为真的代码块。注意即使 6 可以被 2 整除,也不会出现 `number is divisible by 2` 的输出,更不会出现 `else` 块中的 `number is not divisible by 4, 3, or 2`。原因是 Rust 只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至就不会检查剩下的条件了。
|
||||
当执行这个程序时,它按顺序检查每个 `if` 表达式并执行第一个条件为真的代码块。注意即使 6 可以被 2 整除,也不会输出 `number is divisible by 2`,更不会输出 `else` 块中的 `number is not divisible by 4, 3, or 2`。原因是 Rust 只会执行第一个条件为真的代码块,并且一旦它找到一个以后,甚至都不会检查剩下的条件了。
|
||||
|
||||
使用过多的 `else if` 表达式会使代码显得杂乱无章,所以如果有多于一个 `else if`,最好重构代码。为此第六章会介绍 Rust 中一个叫做 `match` 的强大的分支结构(branching construct)。
|
||||
使用过多的 `else if` 表达式会使代码显得杂乱无章,所以如果有多于一个 `else if` 表达式,最好重构代码。为此,第六章会介绍一个强大的 Rust 分支结构(branching construct),叫做 `match`。
|
||||
|
||||
#### 在 `let` 语句中使用 `if`
|
||||
|
||||
@ -154,9 +156,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 3-2:将 `if` 的返回值赋值给一个变量</span>
|
||||
<span class="caption">示例 3-2:将 `if` 表达式的返回值赋给一个变量</span>
|
||||
|
||||
`number` 变量将会绑定到基于 `if` 表达式结果的值。运行这段代码看看会出现什么:
|
||||
`number` 变量将会绑定到表示 `if` 表达式结果的值上。运行这段代码看看会出现什么:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -166,7 +168,7 @@ $ cargo run
|
||||
The value of number is: 5
|
||||
```
|
||||
|
||||
还记得代码块的值是其最后一个表达式的值,以及数字本身也是一个表达式吗。在这个例子中,整个 `if` 表达式的值依赖哪个代码块被执行。这意味着 `if` 的每个分支的可能的返回值都必须是相同类型;在示例 3-2 中,`if` 分支和 `else` 分支的结果都是 `i32` 整型。如果它们的类型不匹配,如下面这个例子,则会出现一个错误:
|
||||
记住,代码块的值是其最后一个表达式的值,而数字本身就是一个表达式。在这个例子中,整个 `if` 表达式的值取决于哪个代码块被执行。这意味着 `if` 的每个分支的可能的返回值都必须是相同类型;在示例 3-2 中,`if` 分支和 `else` 分支的结果都是 `i32` 整型。如果它们的类型不匹配,如下面这个例子,则会出现一个错误:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -184,7 +186,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
当运行这段代码,会得到一个错误。`if` 和 `else` 分支的值类型是不相容的,同时 Rust 也准确地表明了在程序中的何处发现的这个问题:
|
||||
当编译这段代码时,会得到一个错误。`if` 和 `else` 分支的值类型是不相容的,同时 Rust 也准确地指出在程序中的何处发现的这个问题:
|
||||
|
||||
```text
|
||||
error[E0308]: if and else have incompatible types
|
||||
@ -196,19 +198,19 @@ error[E0308]: if and else have incompatible types
|
||||
6 | | } else {
|
||||
7 | | "six"
|
||||
8 | | };
|
||||
| |_____^ expected integral variable, found reference
|
||||
| |_____^ expected integral variable, found &str
|
||||
|
|
||||
= note: expected type `{integer}`
|
||||
found type `&str`
|
||||
```
|
||||
|
||||
`if` 代码块的表达式返回一个整型,而 `else` 代码块返回一个字符串。这并不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 `number` 变量的类型,这样它就可以在编译时证明其他使用 `number` 变量的地方它的类型是有效的。Rust 并不能够在 `number` 的类型只能在运行时确定的情况下工作;这样会使编译器变得更复杂而且只能为代码提供更少的保障,因为它不得不记录所有变量的多种可能的类型。
|
||||
`if` 代码块中的表达式返回一个整数,而 `else` 代码块中的表达式返回一个字符串。这不可行,因为变量必须只有一个类型。Rust 需要在编译时就确切的知道 `number` 变量的类型,这样它就可以在编译时验证在每处使用的 `number` 变量的类型是有效的。Rust 并不能够在 `number` 的类型只能在运行时确定的情况下工作;这样会使编译器变得更复杂而且只能为代码提供更少的保障,因为它不得不记录所有变量的多种可能的类型。
|
||||
|
||||
### 使用循环重复执行
|
||||
|
||||
多次执行同一段代码是很常用的,Rust 为此提供了多种 **循环**(*loops*)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们创建一个叫做 *loops* 的新项目。
|
||||
多次执行同一段代码是很常用的,Rust 为此提供了多种 **循环**(*loops*)。一个循环执行循环体中的代码直到结尾并紧接着回到开头继续执行。为了实验一下循环,让我们新建一个叫做 *loops* 的项目。
|
||||
|
||||
Rust 有三种循环类型:`loop`、`while` 和 `for`。让我们每一个都试试。
|
||||
Rust 有三种循环:`loop`、`while` 和 `for`。我们每一个都试试。
|
||||
|
||||
#### 使用 `loop` 重复执行代码
|
||||
|
||||
@ -226,7 +228,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
当执行这个程序,我们会看到 `again!` 被连续的打印直到我们手动停止程序。大部分终端都支持一个键盘快捷键,<span class="keystroke">ctrl-C</span>,来终止一个陷入无限循环的程序。尝试一下:
|
||||
当运行这个程序时,我们会看到连续的反复打印 `again!`,直到我们手动停止程序。大部分终端都支持一个快捷键,<span class="keystroke">ctrl-c</span>,来终止一个陷入无限循环的程序。尝试一下:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -240,15 +242,15 @@ again!
|
||||
^Cagain!
|
||||
```
|
||||
|
||||
符号 `^C` 代表你在这按下了<span class="keystroke">ctrl-C</span>。在 `^C` 之后你可能看到也可能看不到 `again!` ,这依赖于在接收到终止信号时代码执行到了循环的何处。
|
||||
符号 `^C` 代表你在这按下了<span class="keystroke">ctrl-c</span>。在 `^C` 之后你可能看到也可能看不到 `again!` ,这取决于在接收到终止信号时代码执行到了循环的何处。
|
||||
|
||||
幸运的是,Rust 提供了另一个更可靠的方式来退出循环。可以使用 `break` 关键字来告诉程序何时停止执行循环。回忆一下在第二章猜猜看游戏的 “猜测正确后退出” 部分使用过它来在用户猜对数字赢得游戏后退出程序。
|
||||
幸运的是,Rust 提供了另一种更可靠的退出循环的方式。可以使用 `break` 关键字来告诉程序何时停止循环。回忆一下在第二章猜猜看游戏的 “猜测正确后退出” 部分使用过它来在用户猜对数字赢得游戏后退出程序。
|
||||
|
||||
#### `while` 条件循环
|
||||
|
||||
在程序中计算循环的条件也很常见。当条件为真,执行循环。当条件不再为真,调用 `break` 停止循环。这个循环类型可以通过组合 `loop`、`if`、`else` 和 `break` 来实现;如果你喜欢的话,现在就可以在程序中试试。
|
||||
|
||||
然而,这个模式太常见了以至于 Rust 为此提供了一个内建的语言结构,它被称为 `while` 循环。下面的例子使用了 `while`:程序循环三次,每次数字都减一。接着,在循环之后,打印出另一个信息并退出:
|
||||
然而,这个模式太常用了,Rust 为此内置了一个语言结构,它被称为 `while` 循环。示例 3-3 使用了 `while`:程序循环三次,每次数字都减一。接着,在循环结束后,打印出另一个信息并退出。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -266,11 +268,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
这个结构消除了很多需要嵌套使用 `loop`、`if`、`else` 和 `break` 的代码,这样显得更加清楚。当条件为真就执行,否则退出循环。
|
||||
<span class="caption">示例 3-3: 当条件为真时,使用 `while` 循环运行代码</span>
|
||||
|
||||
这种结构消除了使用 `loop`、`if`、`else` 和 `break` 时必须有的很多嵌套,代码更加清晰。当条件为真就执行,否则退出循环。
|
||||
|
||||
#### 使用 `for` 遍历集合
|
||||
|
||||
可以使用 `while` 结构来遍历一个元素集合,比如数组。例如,看看如下的示例 3-3:
|
||||
可以使用 `while` 结构来遍历集合中的元素,比如数组。例如,看看示例 3-4。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -287,9 +291,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 3-3:使用 `while` 循环遍历集合中的每一个元素</span>
|
||||
<span class="caption">示例 3-4:使用 `while` 循环遍历集合中的元素</span>
|
||||
|
||||
这里代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5` 不再为真)。运行这段代码会打印出数组中的每一个元素:
|
||||
这里,代码对数组中的元素进行计数。它从索引 `0` 开始,并接着循环直到遇到数组的最后一个索引(这时,`index < 5` 不再为真)。运行这段代码会打印出数组中的每一个元素:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
@ -303,11 +307,11 @@ the value is: 40
|
||||
the value is: 50
|
||||
```
|
||||
|
||||
所有数组中的五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 `5`,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
|
||||
数组中的所有五个元素都如期被打印出来。尽管 `index` 在某一时刻会到达值 `5`,不过循环在其尝试从数组获取第六个值(会越界)之前就停止了。
|
||||
|
||||
不过这个过程是容易出错的;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
|
||||
但这个过程很容易出错;如果索引长度不正确会导致程序 panic。这也使程序更慢,因为编译器增加了运行时代码来对每次循环的每个元素进行条件检查。
|
||||
|
||||
可以使用 `for` 循环来对一个集合的每个元素执行一些代码,来作为一个更有效率的替代。`for` 循环看起来如示例 3-4 所示:
|
||||
作为更简洁的替代方案,可以使用 `for` 循环来对一个集合的每个元素执行一些代码。`for` 循环看起来如示例 3-5 所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -321,13 +325,13 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 3-4:使用 `for` 循环遍历集合中的每一个元素</span>
|
||||
<span class="caption">示例 3-5:使用 `for` 循环遍历集合中的元素</span>
|
||||
|
||||
当运行这段代码,将看到与示例 3-3 一样的输出。更为重要的是,我们增强了代码安全性并消除了出现可能会导致超出数组的结尾或遍历长度不够而缺少一些元素这类 bug 的机会。
|
||||
当运行这段代码时,将看到与示例 3-4 一样的输出。更为重要的是,我们增强了代码安全性,并消除了可能由于超出数组的结尾或遍历长度不够而缺少一些元素而导致的 bug。
|
||||
|
||||
例如,在示例 3-3 的代码中,如果从数组 `a` 中移除一个元素但忘记更新条件为 `while index < 4`,代码将会 panic。使用`for`循环的话,就不需要惦记着在更新数组元素数量时修改其他的代码了。
|
||||
例如,在示例 3-4 的代码中,如果从数组 `a` 中移除一个元素但忘记将条件更新为 `while index < 4`,代码将会 panic。使用 `for` 循环的话,就不需要惦记着在改变数组元素个数时修改其他的代码了。
|
||||
|
||||
`for` 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 `while` 循环的倒计时例子,大部分 Rustacean 也会使用 `for` 循环。这么做的方式是使用 `Range`,它是标准库提供的用来生成从一个数字开始到另一个数字之前结束的所有数字序列的类型。
|
||||
`for` 循环的安全性和简洁性使得它成为 Rust 中使用最多的循环结构。即使是在想要循环执行代码特定次数时,例如示例 3-3 中使用 `while` 循环的倒计时例子,大部分 Rustacean 也会使用 `for` 循环。这么做的方式是使用 `Range`,它是标准库提供的类型,用来生成从一个数字开始到另一个数字之前结束的所有数字的序列。
|
||||
|
||||
下面是一个使用 `for` 循环来倒计时的例子,它还使用了一个我们还未讲到的方法,`rev`,用来反转 range:
|
||||
|
||||
@ -346,10 +350,10 @@ fn main() {
|
||||
|
||||
## 总结
|
||||
|
||||
你做到了!这是一个大章节:你学习了变量,标量和 `if` 表达式,还有循环!如果你想要实践本章讨论的概念,尝试构建如下的程序:
|
||||
你做到了!这是一个大章节:你学习了变量、标量和复合数据类型、函数、注释、 `if` 表达式和循环!如果你想要实践本章讨论的概念,尝试构建如下程序:
|
||||
|
||||
* 相互转换摄氏与华氏温度
|
||||
* 生成 n 阶斐波那契数列
|
||||
* 打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分(编写循环)
|
||||
* 相互转换摄氏与华氏温度。
|
||||
* 生成 n 阶斐波那契数列。
|
||||
* 打印圣诞颂歌 “The Twelve Days of Christmas” 的歌词,并利用歌曲中的重复部分(编写循环)。
|
||||
|
||||
当你准备好继续的时候,让我们讨论一个其他语言中 *并不* 常见的概念:所有权(ownership)。
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-00-understanding-ownership.md)
|
||||
> <br>
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
> commit aff4f619c4d6dc138b57b74c3a898ba9bce06649
|
||||
|
||||
所有权(系统)是 Rust 最独特的功能,其令 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。本章我们将讲到所有权以及相关功能:借用、slice 以及 Rust 如何在内存中布局数据。
|
||||
所有权(系统)是 Rust 最独特的功能,其令 Rust 无需垃圾回收(garbage collector)即可保障内存安全。因此,理解 Rust 中所有权如何工作是十分重要的。本章,我们将讲到所有权以及相关功能:借用、slice 以及 Rust 如何在内存中布局数据。
|
@ -1,40 +1,19 @@
|
||||
## `Drop` Trait 运行清理代码
|
||||
## 使用 `Drop` Trait 运行清理代码
|
||||
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch15-03-drop.md)
|
||||
> <br>
|
||||
> commit 721553e3a7b5ee9430cb548c8699b67be197b3f6
|
||||
> commit 6060440d67759b7c8627b4d97cb69576057f5fa6
|
||||
|
||||
对于智能指针模式来说另一个重要的 trait 是 `Drop`。`Drop` 允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box<T>` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
|
||||
|
||||
在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定一些代码应该在值离开作用域时被执行,而编译器会自动插入这些代码。
|
||||
|
||||
<!-- Are we saying that any code can be run, and that we can use that to clean
|
||||
up, or that this code that can be run is specifically always for clean up? -->
|
||||
<!-- I don't understand what the difference between those two choices are?
|
||||
/Carol -->
|
||||
在其他一些语言中,我们不得不记住在每次使用完智能指针实例后调用清理内存或资源的代码。如果忘记的话,运行代码的系统可能会因为负荷过重而崩溃。在 Rust 中,可以指定一些代码应该在值离开作用域时被执行,而编译器会自动插入这些代码。于是我们就不需要在程序中到处编写在实例结束时清理这些变量的代码——而且还不会泄露资源了。
|
||||
|
||||
这意味着无需记住在所有处理完这些类型实例后调用清理代码,而仍然不会泄露资源!
|
||||
|
||||
指定在值离开作用域时应该执行的代码的方式是实现 `Drop` trait。`Drop` trait 要求实现一个叫做 `drop` 的方法,它获取一个 `self` 的可变引用。为了能够看出 Rust 何时调用 `drop`,让我们暂时使用 `println!` 语句实现 `drop`。
|
||||
|
||||
<!-- Why are we showing this as an example and not an example of it being used
|
||||
for clean up? -->
|
||||
<!-- To demonstrate the mechanics of implementing the trait and showing when
|
||||
this code gets run. It's hard to experience the cleaning up unless we print
|
||||
something. /Carol -->
|
||||
|
||||
示例 15-16 展示了唯一定制功能就是当其实例离开作用域时打印出 `Dropping CustomSmartPointer!` 的结构体 `CustomSmartPointer`。这会演示 Rust 何时运行 `drop` 函数:
|
||||
|
||||
<!-- Is this below just telling us how to adapt it for cleaning up instead?
|
||||
Maybe save it for when we have context for it? Instead of a `println!`
|
||||
statement, you'd fill in `drop` with whatever cleanup code your smart pointer
|
||||
needs to run: -->
|
||||
<!-- This is demonstrating what we need to do to use `Drop`, without getting
|
||||
into the complexities of what "cleaning up" might mean yet, just to give the
|
||||
reader an idea of when this code gets called and that it gets called
|
||||
automatically. We're building up to cleaning up. /Carol -->
|
||||
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
@ -55,16 +34,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-16:结构体 `CustomSmartPointer`,其实现了放置清理代码的 `Drop` trait</span>
|
||||
<span class="caption">示例 15-14:结构体 `CustomSmartPointer`,其实现了放置清理代码的 `Drop` trait</span>
|
||||
|
||||
`Drop` trait 包含在 prelude 中,所以无需导入它。我们在 `CustomSmartPointer` 上实现了 `Drop` trait,并提供了一个调用 `println!` 的 `drop` 方法实现。`drop` 函数体是放置任何当类型实例离开作用域时期望运行的逻辑的地方。这里选择打印一些文本以展示 Rust 合适调用 `drop`。
|
||||
|
||||
<!-- Where you'd put this code, or where this code would be called? It seems
|
||||
laborious to write this clean up code wherever there's a print call? -->
|
||||
<!-- I'm not sure how you concluded that from what we had here, could you
|
||||
elaborate? /Carol -->
|
||||
|
||||
在 `main` 中,新建了一个 `CustomSmartPointer` 实例并打印出了 `CustomSmartPointer created.`。在 `main` 的结尾,`CustomSmartPointer` 的实例会离开作用域,而 Rust 会调用放置于 `drop` 方法中的代码,打印出最后的信息。注意无需显示调用 `drop` 方法:
|
||||
在 `main` 中,我们新建了两个 `CustomSmartPointer` 实例并打印出了 `CustomSmartPointer created.`。在 `main` 的结尾,`CustomSmartPointer` 的实例会离开作用域,而 Rust 会调用放置于 `drop` 方法中的代码,打印出最后的信息。注意无需显示调用 `drop` 方法:
|
||||
|
||||
当运行这个程序,会出现如下输出:
|
||||
|
||||
@ -74,27 +48,13 @@ Dropping CustomSmartPointer with data `other stuff`!
|
||||
Dropping CustomSmartPointer with data `my stuff`!
|
||||
```
|
||||
|
||||
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创创建时相反的顺序被丢弃,所以 `d` 在 `c` 之前被丢弃。这刚好给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
|
||||
|
||||
<!-- Can you wrap this example up by saying what you would actually put in a
|
||||
drop method and why?-->
|
||||
<!-- Done /Carol -->
|
||||
当实例离开作用域 Rust 会自动调用 `drop`,并调用我们指定的代码。变量以被创建时相反的顺序被丢弃,所以 `d` 在 `c` 之前被丢弃。这个例子刚好给了我们一个 drop 方法如何工作的可视化指导,不过通常需要指定类型所需执行的清理代码而不是打印信息。
|
||||
|
||||
#### 通过 `std::mem::drop` 提早丢弃值
|
||||
|
||||
<!-- is this a new method from Drop or the same method? -->
|
||||
<!-- This is a new function. /Carol -->
|
||||
不幸的是,我们并不能直截了当的禁用 `drop` 这个功能。通常也不需要禁用 `drop` ;整个 `Drop` trait 存在的意义在于其是自动处理的。然而,有时你可能需要提早清理某个值。一个例子是当使用智能指针管理锁时;你可能希望强制运行 `drop` 方法来释放锁以便作用域中的其他代码可以获取锁。Rust 并不允许我们主动调用 `Drop` trait 的 `drop` 方法;当我们希望在作用域结束之前就释放变量的话,我们应该使用的是由标准库提供的 `std::mem::drop`。
|
||||
|
||||
Rust 当值离开作用域时自动插入 `drop` 调用,不能直接禁用这个功能。
|
||||
|
||||
|
||||
|
||||
被打印到屏幕上,它展示了 Rust 在实例离开作用域时自动调用了`drop`。通常也不需要禁用 `drop`;整个 `Drop` trait 存在的意义在于其是自动处理的。有时可能需要提早清理某个值。一个例子是当使用智能指针管理锁时;你可能希望强制运行 `drop` 方法来释放锁以便作用域中的其他代码可以获取锁。首先。让我们看看自己调用 `Drop` trait 的 `drop` 方法会发生什么,如示例 15-17 修改示例 15-16 中的 `main` 函数:
|
||||
|
||||
<!-- Above: I'm not following why we are doing this, if it's not necessary and
|
||||
we aren't going to cover it now anyway -- can you lay out why we're discussing
|
||||
this here? -->
|
||||
<!-- Done. /Carol -->
|
||||
如果我们像是示例 15-14 那样尝试调用 `Drop` trait 的 `drop` 方法,就会得到像示例 15-15 那样的编译错误:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -107,15 +67,15 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-17:尝试手动调用 `Drop` trait 的 `drop` 方法提早清理</span>
|
||||
<span class="caption">示例 15-15:尝试手动调用 `Drop` trait 的 `drop` 方法提早清理</span>
|
||||
|
||||
如果尝试编译代码会得到如下错误:
|
||||
|
||||
```text
|
||||
error[E0040]: explicit use of destructor method
|
||||
--> src/main.rs:15:7
|
||||
--> src/main.rs:14:7
|
||||
|
|
||||
15 | c.drop();
|
||||
14 | c.drop();
|
||||
| ^^^^ explicit destructor calls not allowed
|
||||
```
|
||||
|
||||
@ -125,7 +85,7 @@ Rust 不允许我们显式调用 `drop` 因为 Rust 仍然会在 `main` 的结
|
||||
|
||||
因为不能禁用当值离开作用域时自动插入的 `drop`,并且不能显示调用 `drop`,如果我们需要提早清理值,可以使用 `std::mem::drop` 函数。
|
||||
|
||||
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude,所以我们可以修改示例 15-16 中的 `main` 来调用 `drop` 函数如示例 15-18 所示:
|
||||
`std::mem::drop` 函数不同于 `Drop` trait 中的 `drop` 方法。可以通过传递希望提早强制丢弃的值作为参数。`std::mem::drop` 位于 prelude,所以我们可以修改示例 15-15 中的 `main` 来调用 `drop` 函数如示例 15-16 所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -148,7 +108,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-18: 在值离开作用域之前调用 `std::mem::drop` 显式清理</span>
|
||||
<span class="caption">示例 15-16: 在值离开作用域之前调用 `std::mem::drop` 显式清理</span>
|
||||
|
||||
运行这段代码会打印出如下:
|
||||
|
||||
@ -158,24 +118,12 @@ Dropping CustomSmartPointer with data `some data`!
|
||||
CustomSmartPointer dropped before the end of main.
|
||||
```
|
||||
|
||||
<!-- What's the destructor code, here? We haven't mentioned that before, not in
|
||||
this chapter in any case -->
|
||||
<!-- I added a definition for destructor a few paragraphs above, the first time
|
||||
we see it in an error message. /Carol -->
|
||||
|
||||
```Dropping CustomSmartPointer with data `some data`!``` 出现在 `CustomSmartPointer created.` 和 `CustomSmartPointer dropped before the end of main.` 之间,表明了 `drop` 方法被调用了并在此丢弃了 `c`。
|
||||
|
||||
<!-- How does this show that the destructor code (is that drop?) is called? Is
|
||||
this correct, above?-->
|
||||
<!-- The order of what gets printed shows that the drop code is called.
|
||||
/Carol-->
|
||||
|
||||
`Drop` trait 实现中指定的代码可以用于许多方面来使得清理变得方便和安全:比如可以用其创建我们自己的内存分配器!通过 `Drop` trait 和 Rust 所有权系统,你无需担心之后清理代码,Rust 会自动考虑这些问题。
|
||||
|
||||
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 `drop` 只会在值不再被使用时被调用一次。
|
||||
|
||||
|
||||
|
||||
使用`Drop` trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过`Drop` trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了`drop`只会在值不再被使用时被调用一次。
|
||||
使用 `Drop` trait 实现指定的代码在很多方面都使得清理值变得方便和安全:比如可以使用它来创建我们自己的内存分配器!通过`Drop` trait 和 Rust 所有权系统,就无需担心之后清理代码,因为 Rust 会自动考虑这些问题。如果代码在值仍被使用时就清理它会出现编译错误,因为所有权系统确保了引用总是有效的,这也就保证了`drop`只会在值不再被使用时被调用一次。
|
||||
|
||||
现在我们学习了 `Box<T>` 和一些智能指针的特性,让我们聊聊一些其他标准库中定义的智能指针。
|
Loading…
Reference in New Issue
Block a user