mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2025-02-22 20:22:18 +08:00
Merge branch 'master' of github.com:Orefa/trpl-zh-cn
This commit is contained in:
commit
352b3c3919
44
.github/workflows/main.yml
vendored
Normal file
44
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: CI
|
||||
on:
|
||||
# Trigger the workflow on push or pull request,
|
||||
# but only for the main branch
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- name: Update rustup
|
||||
run: rustup self update
|
||||
- name: Install Rust
|
||||
run: |
|
||||
rustup set profile minimal
|
||||
rustup toolchain install 1.52 -c rust-docs
|
||||
rustup default 1.52
|
||||
- name: Install mdbook
|
||||
run: |
|
||||
mkdir bin
|
||||
curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.5/mdbook-v0.4.5-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=bin
|
||||
echo "$(pwd)/bin" >> ${GITHUB_PATH}
|
||||
- name: Report versions
|
||||
run: |
|
||||
rustup --version
|
||||
rustc -Vv
|
||||
mdbook --version
|
||||
- name: Run mdBook Build
|
||||
run: mdbook build
|
||||
- name: Deploy gh-pages
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./book
|
||||
force_orphan: true
|
||||
enable_jekyll: true
|
22
.travis.yml
22
.travis.yml
@ -1,22 +0,0 @@
|
||||
sudo: false
|
||||
dist: trusty
|
||||
language: rust
|
||||
cache: cargo
|
||||
rust:
|
||||
- 1.44.0
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
before_script:
|
||||
- (cargo install mdbook --vers 0.4.1 --force || true)
|
||||
script:
|
||||
- mdbook build
|
||||
- echo 'nojekyll' > book/.nojekyll
|
||||
deploy:
|
||||
provider: pages
|
||||
local-dir: book
|
||||
skip-cleanup: true
|
||||
github-token: $GITHUB_TOKEN
|
||||
keep-history: false
|
||||
on:
|
||||
branch: master
|
13
README.md
13
README.md
@ -1,6 +1,6 @@
|
||||
# Rust 程序设计语言(第二版 & 2018 edition) 简体中文版
|
||||
|
||||
[](https://travis-ci.org/KaiserY/trpl-zh-cn)
|
||||

|
||||
|
||||
## 状态
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
|
||||
PS:
|
||||
|
||||
* 对照源码位置:[https://github.com/rust-lang/book/tree/master/src][source]
|
||||
* 对照源码位置:[https://github.com/rust-lang/book/tree/main/src][source]
|
||||
* 每章翻译开头都带有官方链接和 commit hash,若发现与官方不一致,欢迎 Issue 或 PR :)
|
||||
|
||||
[source]: https://github.com/rust-lang/book/tree/master/src
|
||||
[source]: https://github.com/rust-lang/book/tree/main/src
|
||||
|
||||
## 静态页面构建与文档撰写
|
||||
|
||||
@ -45,8 +45,9 @@ vuepress dev ./src
|
||||
|
||||
- Rust语言中文社区:<https://rust.cc/>
|
||||
- Rust 中文 Wiki:<https://wiki.rust-china.org/>
|
||||
- Rust编程语言社区主群:303838735
|
||||
- Rust 水群:253849562
|
||||
- Rust编程语言社区1群,群号:303838735(已满,只能内部邀请)
|
||||
- Rust编程语言社区2群,群号:813448660
|
||||
- Rust水群(编程社区子群),电报群:[t.me/rust_deep_water](//t.me/rust_deep_water)
|
||||
|
||||
## GitBook
|
||||
|
||||
@ -55,4 +56,4 @@ vuepress dev ./src
|
||||
本翻译加速查看站点有:
|
||||
- 深圳站点:<http://120.78.128.153/rustbook>
|
||||
|
||||
[GitBook.com](https://www.gitbook.com/) 地址:<https://legacy.gitbook.com/book/kaisery/trpl-zh-cn/details>
|
||||
[GitBook.com](https://www.gitbook.com/) 地址:<https://kaisery.github.io/trpl-zh-cn/>
|
||||
|
92
ferris.js
92
ferris.js
@ -1,51 +1,47 @@
|
||||
var ferrisTypes = [
|
||||
{
|
||||
attr: 'does_not_compile',
|
||||
title: '这些代码不能编译!'
|
||||
},
|
||||
{
|
||||
attr: 'panics',
|
||||
title: '这些代码会 panic!'
|
||||
},
|
||||
{
|
||||
attr: 'unsafe',
|
||||
title: '这些代码块包含不安全(unsafe)代码。'
|
||||
},
|
||||
{
|
||||
attr: 'not_desired_behavior',
|
||||
title: '这些代码不会产生期望的行为。'
|
||||
}
|
||||
]
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
for (var ferrisType of ferrisTypes) {
|
||||
attachFerrises(ferrisType)
|
||||
}
|
||||
})
|
||||
|
||||
function attachFerrises (type) {
|
||||
var elements = document.getElementsByClassName(type.attr)
|
||||
|
||||
for (var codeBlock of elements) {
|
||||
var lines = codeBlock.textContent.split(/\r|\r\n|\n/).length - 1;
|
||||
|
||||
if (lines >= 4) {
|
||||
attachFerris(codeBlock, type)
|
||||
}
|
||||
{
|
||||
attr: 'does_not_compile',
|
||||
title: '这些代码不能编译!'
|
||||
},
|
||||
{
|
||||
attr: 'panics',
|
||||
title: '这些代码会 panic!'
|
||||
},
|
||||
{
|
||||
attr: 'not_desired_behavior',
|
||||
title: '这些代码不会产生期望的行为。'
|
||||
}
|
||||
]
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
for (var ferrisType of ferrisTypes) {
|
||||
attachFerrises(ferrisType)
|
||||
}
|
||||
})
|
||||
|
||||
function attachFerrises (type) {
|
||||
var elements = document.getElementsByClassName(type.attr)
|
||||
|
||||
for (var codeBlock of elements) {
|
||||
var lines = codeBlock.textContent.split(/\r|\r\n|\n/).length - 1;
|
||||
|
||||
if (lines >= 4) {
|
||||
attachFerris(codeBlock, type)
|
||||
}
|
||||
}
|
||||
|
||||
function attachFerris (element, type) {
|
||||
var a = document.createElement('a')
|
||||
a.setAttribute('href', 'ch00-00-introduction.html#ferris')
|
||||
a.setAttribute('target', '_blank')
|
||||
|
||||
var img = document.createElement('img')
|
||||
img.setAttribute('src', 'img/ferris/' + type.attr + '.svg')
|
||||
img.setAttribute('title', type.title)
|
||||
img.className = 'ferris'
|
||||
|
||||
a.appendChild(img)
|
||||
|
||||
element.parentElement.insertBefore(a, element)
|
||||
}
|
||||
}
|
||||
|
||||
function attachFerris (element, type) {
|
||||
var a = document.createElement('a')
|
||||
a.setAttribute('href', 'ch00-00-introduction.html#ferris')
|
||||
a.setAttribute('target', '_blank')
|
||||
|
||||
var img = document.createElement('img')
|
||||
img.setAttribute('src', 'img/ferris/' + type.attr + '.svg')
|
||||
img.setAttribute('title', type.title)
|
||||
img.className = 'ferris'
|
||||
|
||||
a.appendChild(img)
|
||||
|
||||
element.parentElement.insertBefore(a, element)
|
||||
}
|
@ -110,7 +110,7 @@
|
||||
|
||||
- [模式用来匹配值的结构](ch18-00-patterns.md)
|
||||
- [所有可能会用到模式的位置](ch18-01-all-the-places-for-patterns.md)
|
||||
- [Refutability:何时模式可能会匹配失败](ch18-02-refutability.md)
|
||||
- [Refutability(可反驳性): 模式是否会匹配失效](ch18-02-refutability.md)
|
||||
- [模式的全部语法](ch18-03-pattern-syntax.md)
|
||||
|
||||
- [高级特征](ch19-00-advanced-features.md)
|
||||
|
@ -108,9 +108,9 @@
|
||||
|
||||
## 高级主题
|
||||
|
||||
- [模式用来匹配值的结构](ch18-00-patterns.md)
|
||||
- [模式与模式匹配](ch18-00-patterns.md)
|
||||
- [所有可能会用到模式的位置](ch18-01-all-the-places-for-patterns.md)
|
||||
- [Refutability:何时模式可能会匹配失败](ch18-02-refutability.md)
|
||||
- [Refutability(可反驳性): 模式是否会匹配失效](ch18-02-refutability.md)
|
||||
- [模式的全部语法](ch18-03-pattern-syntax.md)
|
||||
|
||||
- [高级特征](ch19-00-advanced-features.md)
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 附录
|
||||
|
||||
> [appendix-00.md](https://github.com/rust-lang/book/blob/master/src/appendix-00.md)
|
||||
> [appendix-00.md](https://github.com/rust-lang/book/blob/main/src/appendix-00.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 A:关键字
|
||||
|
||||
> [appendix-01-keywords.md](https://raw.githubusercontent.com/rust-lang/book/master/src/appendix-01-keywords.md)
|
||||
> [appendix-01-keywords.md](https://github.com/rust-lang/book/blob/main/src/appendix-01-keywords.md)
|
||||
> <br>
|
||||
> commit 27dd97a785794709aa87c51ab697cded41e8163a
|
||||
|
||||
@ -108,6 +108,6 @@ fn main() {
|
||||
|
||||
此代码编译没有任何错误。注意 `r#` 前缀需同时用于函数名定义和 `main` 函数中的调用。
|
||||
|
||||
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。 此外,原始标识符允许你使用以不同于你的 crate 使用的 Rust 版本编写的库。比如,`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符语法,在这里是 `r#try`。有关版本的更多信息,请参见[附录 E][appendix-e].
|
||||
原始标识符允许使用你选择的任何单词作为标识符,即使该单词恰好是保留关键字。 此外,原始标识符允许你使用以不同于你的 crate 使用的 Rust 版本编写的库。比如,`try` 在 2015 edition 中不是关键字,而在 2018 edition 则是。所以如果用 2015 edition 编写的库中带有 `try` 函数,在 2018 edition 中调用时就需要使用原始标识符语法,在这里是 `r#try`。有关版本的更多信息,请参见[附录 E][appendix-e].
|
||||
|
||||
[appendix-e]: appendix-05-editions.html
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 B:运算符与符号
|
||||
|
||||
> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/master/src/appendix-02-operators.md)
|
||||
> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/main/src/appendix-02-operators.md)
|
||||
> <br />
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 C:可派生的 trait
|
||||
|
||||
> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/master/src/appendix-03-derivable-traits.md)
|
||||
> [appendix-03-derivable-traits.md](https://github.com/rust-lang/book/blob/main/src/appendix-03-derivable-traits.md)
|
||||
> <br />
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 D:实用开发工具
|
||||
|
||||
> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/master/src/appendix-04-useful-development-tools.md)
|
||||
> [appendix-04-useful-development-tools.md](https://github.com/rust-lang/book/blob/main/src/appendix-04-useful-development-tools.md)
|
||||
> <br />
|
||||
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 E:版本
|
||||
|
||||
> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/master/src/appendix-05-editions.md)
|
||||
> [appendix-05-editions.md](https://github.com/rust-lang/book/blob/main/src/appendix-05-editions.md)
|
||||
> <br />
|
||||
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 F:本书译本
|
||||
|
||||
> [appendix-06-translation.md](https://github.com/rust-lang/book/blob/master/src/appendix-06-translation.md)
|
||||
> [appendix-06-translation.md](https://github.com/rust-lang/book/blob/main/src/appendix-06-translation.md)
|
||||
> <br />
|
||||
> commit 72900e05f04ae60e06c2665567771bdd8befa89c
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 附录 G:Rust 是如何开发的与 “Nightly Rust”
|
||||
|
||||
> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/master/src/appendix-07-nightly-rust.md)
|
||||
> [appendix-07-nightly-rust.md](https://github.com/rust-lang/book/blob/main/src/appendix-07-nightly-rust.md)
|
||||
> <br />
|
||||
> commit 70a82519e48b8a61f98cabb8ff443d1b21962fea
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 介绍
|
||||
|
||||
> [ch00-00-introduction.md](https://github.com/rust-lang/book/blob/master/src/ch00-00-introduction.md)
|
||||
> [ch00-00-introduction.md](https://github.com/rust-lang/book/blob/main/src/ch00-00-introduction.md)
|
||||
> <br>
|
||||
> commit 0aa307c7d79d2cbf83cdf5d47780b2904e9cb03f
|
||||
> commit 4921fde29ae8ccf67d5893d4e43d74284626fded
|
||||
|
||||
> 注意:本书的版本与出版的 [The Rust Programming Language][nsprust]
|
||||
> 和电子版的 [No Starch Press][nsp] 一致
|
||||
@ -78,14 +78,13 @@ Rust 语言也希望能支持很多其他用户,这里提及的只是最大的
|
||||
|
||||
<span id="ferris"></span>
|
||||
|
||||
学习 Rust 的过程中一个重要的部分是学习如何阅读编译器提供的错误信息:它们会指导你编写出能工作的代码。为此,我们会提供很多不能编译的示例代码,以及各个情况下编译器会展示的错误信息。请注意如果随便输入并运行随机的示例代码,它们可能无法编译!请确保阅读任何你尝试运行的示例周围的内容,检视他们是否有意写错。Ferris 也会帮助你区别那些有意无法工作的代码:
|
||||
学习 Rust 的过程中一个重要的部分是学习如何阅读编译器提供的错误信息:它们会指导你编写出能工作的代码。为此,我们会提供很多不能编译的示例,以及各个情况下编译器会展示的错误信息。请注意如果随便输入并运行随机的示例代码,它们可能无法编译!请确保阅读任何你尝试运行的示例周围的内容,检视他们是否有意写错。Ferris 也会帮助你区别那些有意无法工作的代码:
|
||||
|
||||
| Ferris | 意义 |
|
||||
|------------------------------------------------------------------------|--------------------------------------------------|
|
||||
| <img src="img/ferris/does_not_compile.svg" class="ferris-explain"> | 这些代码不能编译! |
|
||||
| <img src="img/ferris/panics.svg" class="ferris-explain"> | 这些代码会 panic! |
|
||||
| <img src="img/ferris/unsafe.svg" class="ferris-explain"> | 这些代码块包含不安全(unsafe)代码。 |
|
||||
| <img src="img/ferris/not_desired_behavior.svg" class="ferris-explain"> | 这些代码不会产生期望的行为。 |
|
||||
| <img src="img/ferris/does_not_compile.svg" class="ferris-explain" alt="Ferris with a question mark"> | 这些代码不能编译! |
|
||||
| <img src="img/ferris/panics.svg" class="ferris-explain" alt="Ferris throwing up their hands"> | 这些代码会 panic! |
|
||||
| <img src="img/ferris/not_desired_behavior.svg" class="ferris-explain" alt="Ferris with one claw up, shrugging"> | 这些代码不会产生期望的行为。 |
|
||||
|
||||
在大部分情况,我们会指引你将任何不能编译的代码纠正为正确版本。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 入门指南
|
||||
|
||||
> [ch01-00-getting-started.md](https://github.com/rust-lang/book/blob/master/src/ch01-00-getting-started.md)
|
||||
> [ch01-00-getting-started.md](https://github.com/rust-lang/book/blob/main/src/ch01-00-getting-started.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
## 安装
|
||||
|
||||
> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/src/ch01-01-installation.md)
|
||||
> <br>
|
||||
> commit 27e741b227b6b946a1498ecc9d9dd1bff5819b82
|
||||
> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/main/src/ch01-01-installation.md) <br>
|
||||
> commit a01b23fee4c76a7e0d8c8c3261bdcf3a9840ccd3
|
||||
|
||||
第一步是安装 Rust。我们通过 `rustup` 下载 Rust,这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要联网。
|
||||
第一步是安装 Rust。我们会通过 `rustup` 下载 Rust,这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要联网。
|
||||
|
||||
> 注意:如果你出于某些理由倾向于不使用 `rustup`,请到 [Rust 安装页面](https://www.rust-lang.org/install.html) 查看其它安装选项。
|
||||
> 注意:如果你出于某些理由倾向于不使用 `rustup`,请参阅 [Rust 的其他安装方法页面](https://forge.rust-lang.org/infra/other-installation-methods.html) 了解更多选项。
|
||||
|
||||
接下来的步骤会安装最新的稳定版 Rust 编译器。Rust 的稳定性确保本书所有示例在最新版本的 Rust 中能够继续编译。不同版本的输出可能略有不同,因为 Rust 经常改进错误信息和警告。也就是说,任何通过这些步骤安装的最新稳定版 Rust,都应该能正常运行本书中的内容。
|
||||
|
||||
@ -19,7 +18,7 @@
|
||||
如果你使用 Linux 或 macOS,打开终端并输入如下命令:
|
||||
|
||||
```text
|
||||
$ curl https://sh.rustup.rs -sSf | sh
|
||||
$ curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
|
||||
```
|
||||
|
||||
此命令下载一个脚本并开始安装 `rustup` 工具,这会安装最新稳定版 Rust。过程中可能会提示你输入密码。如果安装成功,将会出现如下内容:
|
||||
@ -28,28 +27,22 @@ $ curl https://sh.rustup.rs -sSf | sh
|
||||
Rust is installed now. Great!
|
||||
```
|
||||
|
||||
如果你愿意的话,可在运行前下载并检查该脚本。
|
||||
另外,你还需要一个链接器(linker),这是 Rust 用来将其编译的输出连接到一个文件中的程序。很可能你已经有一个了。如果你遇到了链接器错误,请尝试安装一个 C 编译器,它通常包括一个链接器。C 编译器也很有用,因为一些常见的 Rust 包依赖于 C 代码,因此需要安装一个 C 编译器。
|
||||
|
||||
此安装脚本自动将 Rust 加入系统 PATH 环境变量中,在下一次登录时生效。如果你希望立刻就开始使用 Rust 而不重启终端,在 shell 中运行如下命令,手动将 Rust 加入系统 PATH 变量中:
|
||||
在 macOS 上,你可以通过运行以下命令获得 C 语言编译器:
|
||||
|
||||
```text
|
||||
$ source $HOME/.cargo/env
|
||||
```console
|
||||
$ xcode-select --install
|
||||
```
|
||||
|
||||
或者,可以在 *~/.bash_profile* 文件中增加如下行:
|
||||
|
||||
```text
|
||||
$ export PATH="$HOME/.cargo/bin:$PATH"
|
||||
```
|
||||
|
||||
另外,你需要一个某种类型的链接器(linker)。很有可能已经安装,不过当你尝试编译 Rust 程序时,却有错误指出无法执行链接器,这意味着你的系统上没有安装链接器,你需要自行安装一个。C 编译器通常带有正确的链接器。请查看你使用平台的文档,了解如何安装 C 编译器。并且,一些常用的 Rust 包依赖 C 代码,也需要安装 C 编译器。因此现在安装一个是值得的。
|
||||
根据发行版的文档,Linux 用户通常应该安装 GCC 或 Clang。例如,如果你使用 Ubuntu,则可以安装 则可以安装 `build-essential` 包。
|
||||
|
||||
### 在 Windows 上安装 `rustup`
|
||||
|
||||
在 Windows 上,前往 [https://www.rust-lang.org/install.html][install] 并按照说明安装 Rust。在安装过程的某个步骤,你会收到一个信息说明为什么需要安装 Visual Studio 2013 或更新版本的 C++ build tools。获取这些 build tools 最方便的方法是安装 [Build Tools for Visual Studio 2019][visualstudio]。这个工具在 “Other Tools and Frameworks” 部分。
|
||||
在 Windows 上,前往 [https://www.rust-lang.org/install.html][install] 并按照说明安装 Rust。在安装过程的某个步骤,你会收到一个信息说明为什么需要安装 Visual Studio 2013 或更新版本的 C++ build tools。获取这些 build tools 最方便的方法是安装 [Build Tools for Visual Studio 2019][visualstudio]。当被问及需要安装什么的时候请确保选择 ”C++ build tools“,并确保包括了 Windows 10 SDK 和英文语言包(English language pack)组件。
|
||||
|
||||
[install]: https://www.rust-lang.org/tools/install
|
||||
[visualstudio]: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019
|
||||
[visualstudio]: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||
|
||||
本书的余下部分会使用能同时运行于 *cmd.exe* 和 PowerShell 的命令。如果存在特定差异,我们会解释使用哪一个。
|
||||
|
||||
@ -85,7 +78,7 @@ rustc x.y.z (abcabcabc yyyy-mm-dd)
|
||||
|
||||
[discord]: https://discord.gg/rust-lang
|
||||
[users]: https://users.rust-lang.org/
|
||||
[stackoverflow]: http://stackoverflow.com/questions/tagged/rust
|
||||
[stackoverflow]: https://stackoverflow.com/questions/tagged/rust
|
||||
|
||||
> 译者:恭喜入坑!(此处应该有掌声!)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## Hello, World!
|
||||
|
||||
> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/master/src/ch01-02-hello-world.md)
|
||||
> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/main/src/ch01-02-hello-world.md)
|
||||
> <br>
|
||||
> commit f63a103270ec8416899675a9cdb1c5cf6d77a498
|
||||
> commit ee7c5776a958353a2be0e3787fffa9523638d0fa
|
||||
|
||||
既然安装好了 Rust,我们来编写第一个 Rust 程序。当学习一门新语言的时候,使用该语言在屏幕上打印 `Hello, world!` 是一项传统,我们将沿用这一传统!
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
|
||||
对于 Linux、macOS 和 Windows PowerShell,输入:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ mkdir ~/projects
|
||||
$ cd ~/projects
|
||||
$ mkdir hello_world
|
||||
@ -50,7 +50,7 @@ fn main() {
|
||||
|
||||
保存文件,并回到终端窗口。在 Linux 或 macOS 上,输入如下命令,编译并运行文件:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ rustc main.rs
|
||||
$ ./main
|
||||
Hello, world!
|
||||
@ -82,7 +82,9 @@ fn main() {
|
||||
|
||||
还须注意,函数体被包裹在花括号中,`{}`。Rust 要求所有函数体都要用花括号包裹起来。一般来说,将左花括号与函数声明置于同一行并以空格分隔,是良好的代码风格。
|
||||
|
||||
在编写本书的时候,一个叫做 `rustfmt` 的自动格式化工具正在开发中。如果你希望在 Rust 项目中保持一种标准风格,`rustfmt` 会将代码格式化为特定的风格。Rust 团队计划最终将该工具包含在标准 Rust 发行版中,就像 `rustc`。所以根据你阅读本书的时间,它可能已经安装到你的电脑中了!检查在线文档以了解更多细节。
|
||||
如果要在Rust项目中坚持使用标准样式,可以使用名为rustfmt的自动格式化工具将代码格式化为特定样式。Rust团队已经在标准的Rust发行版中包含了这个工具,比如rustc,所以它应该已经安装在您的计算机上了!有关更多详细信息,请查看在线文档。
|
||||
|
||||
如果你希望在 Rust 项目中保持一种标准风格,可以使用名为 `rustfmt` 的自动格式化工具将代码格式化为特定的风格。Rust 团队已经在标准的 Rust 发行版中包含了这个工具,就像 `rustc`。所以它应该已经安装在你的电脑中了!检查在线文档以了解更多细节。
|
||||
|
||||
在 `main()` 函数中是如下代码:
|
||||
|
||||
@ -92,7 +94,7 @@ fn main() {
|
||||
|
||||
这行代码完成这个简单程序的所有工作:在屏幕上打印文本。这里有四个重要的细节需要注意。首先 Rust 的缩进风格使用 4 个空格,而不是 1 个制表符(tab)。
|
||||
|
||||
第二,`println!` 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 `println`(没有`!`)。我们将在第十九章详细讨论宏。现在你只需记住,当看到符号 `!` 的时候,就意味着调用的是宏而不是普通函数。
|
||||
第二,`println!` 调用了一个 Rust 宏(macro)。如果是调用函数,则应输入 `println`(没有`!`)。我们将在第十九章详细讨论宏。现在你只需记住,当看到符号 `!` 的时候,就意味着调用的是宏而不是普通函数,并且宏并不总是遵循与函数相同的规则。
|
||||
|
||||
第三,`"Hello, world!"` 是一个字符串。我们把这个字符串作为一个参数传递给 `println!`,字符串将被打印到屏幕上。
|
||||
|
||||
@ -104,7 +106,7 @@ fn main() {
|
||||
|
||||
在运行 Rust 程序之前,必须先使用 Rust 编译器编译它,即输入 `rustc` 命令并传入源文件名称,如下:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ rustc main.rs
|
||||
```
|
||||
|
||||
@ -112,7 +114,7 @@ $ rustc main.rs
|
||||
|
||||
在 Linux、macOS 或 Windows 的 PowerShell 上,在 shell 中输入 `ls` 命令可以看见这个可执行文件。在 Linux 和 macOS,你会看到两个文件。在 Windows PowerShell 中,你会看到同使用 CMD 相同的三个文件。
|
||||
|
||||
```text
|
||||
```console
|
||||
$ ls
|
||||
main main.rs
|
||||
```
|
||||
@ -128,7 +130,7 @@ main.rs
|
||||
|
||||
这展示了扩展名为 *.rs* 的源文件、可执行文件(在 Windows 下是 *main.exe*,其它平台是 *main*),以及当使用 CMD 时会有一个包含调试信息、扩展名为 *.pdb* 的文件。从这里开始运行 *main* 或 *main.exe* 文件,如下:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ ./main # Windows 是 .\main.exe
|
||||
```
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## Hello, Cargo!
|
||||
|
||||
> [ch01-03-hello-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch01-03-hello-cargo.md)
|
||||
> [ch01-03-hello-cargo.md](https://github.com/rust-lang/book/blob/main/src/ch01-03-hello-cargo.md)
|
||||
> <br>
|
||||
> commit f63a103270ec8416899675a9cdb1c5cf6d77a498
|
||||
> commit c032e945406dd8ef2ce2062aa1f9d65dbf9020d6
|
||||
|
||||
Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Cargo 来管理他们的 Rust 项目,因为它可以为你处理很多任务,比如构建代码、下载依赖库并编译这些库。(我们把代码所需要的库叫做 **依赖**(*dependencies*))。
|
||||
|
||||
@ -10,7 +10,7 @@ Cargo 是 Rust 的构建系统和包管理器。大多数 Rustacean 们使用 Ca
|
||||
|
||||
由于绝大多数 Rust 项目使用 Cargo,本书接下来的部分假设你也使用 Cargo。如果使用 [“安装”][installation] 部分介绍的官方安装包的话,则自带了 Cargo。如果通过其他方式安装的话,可以在终端输入如下命令检查是否安装了 Cargo:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo --version
|
||||
```
|
||||
|
||||
@ -18,16 +18,16 @@ $ cargo --version
|
||||
|
||||
### 使用 Cargo 创建项目
|
||||
|
||||
我们使用 Cargo 创建一个新项目,然后看看与上面的 Hello, world! 项目有什么不同。回到 *projects* 目录(或者你存放代码的目录)。接着,可在任何操作系统下运行以下命令:
|
||||
我们使用 Cargo 创建一个新项目,然后看看与上面的 “Hello, world!” 项目有什么不同。回到 *projects* 目录(或者你存放代码的目录)。接着,可在任何操作系统下运行以下命令:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo new hello_cargo
|
||||
$ cd hello_cargo
|
||||
```
|
||||
|
||||
第一行命令新建了名为 *hello_cargo* 的目录。我们将项目命名为 *hello_cargo*,同时 Cargo 在一个同名目录中创建项目文件。
|
||||
|
||||
进入 *hello_cargo* 目录并列出文件。将会看到 Cargo 生成了两个文件和一个目录:一个 *Cargo.toml* 文件,一个 *src* 目录,以及位于 *src* 目录中的 *main.rs* 文件。它也在 *hello_cargo* 目录初始化了一个 git 仓库,以及一个 *.gitignore* 文件。
|
||||
进入 *hello_cargo* 目录并列出文件。将会看到 Cargo 生成了两个文件和一个目录:一个 *Cargo.toml* 文件,一个 *src* 目录,以及位于 *src* 目录中的 *main.rs* 文件。它也在 *hello_cargo* 目录初始化了一个 git 仓库,以及一个 *.gitignore* 文件。如果你在现有的 git 仓库中运行 `cargo new`,则不会生成 git 文件;你可以通过使用`cargo new --vcs=git` 来覆盖此行为。
|
||||
|
||||
> 注意:Git 是一个常用的版本控制系统(version control system, VCS)。可以通过 `--vcs` 参数使 `cargo new` 切换到其它版本控制系统(VCS),或者不使用 VCS。运行 `cargo new --help` 参看可用的选项。
|
||||
|
||||
@ -39,7 +39,6 @@ $ cd hello_cargo
|
||||
[package]
|
||||
name = "hello_cargo"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
@ -47,13 +46,11 @@ edition = "2018"
|
||||
|
||||
<span class="caption">示例 1-2: *cargo new* 命令生成的 *Cargo.toml* 的内容</span>
|
||||
|
||||
这个文件使用 [*TOML*][toml]<!-- ignore --> (*Tom's Obvious, Minimal Language*) 格式,这是 Cargo 配置文件的格式。
|
||||
|
||||
[toml]: https://github.com/toml-lang/toml
|
||||
这个文件使用 [*TOML*](https://toml.io)<!-- ignore --> (*Tom's Obvious, Minimal Language*) 格式,这是 Cargo 配置文件的格式。
|
||||
|
||||
第一行,`[package]`,是一个片段(section)标题,表明下面的语句用来配置一个包。随着我们在这个文件增加更多的信息,还将增加其他片段(section)。
|
||||
|
||||
接下来的四行设置了 Cargo 编译程序所需的配置:项目的名称、版本、作者以及要使用的 Rust 版本。Cargo 从环境中获取你的名字和 email 信息,所以如果这些信息不正确,请修改并保存此文件。附录 E 会介绍 `edition` 的值。
|
||||
接下来的三行设置了 Cargo 编译程序所需的配置:项目的名称、版本以及要使用的 Rust 版本。[附录 E][appendix-e]<!-- ignore --> 会介绍 `edition` 的值。
|
||||
|
||||
最后一行,`[dependencies]`,是罗列项目依赖的片段的开始。在 Rust 中,代码包被称为 *crates*。这个项目并不需要其他的 crate,不过在第二章的第一个项目会用到依赖,那时会用得上这个片段。
|
||||
|
||||
@ -77,7 +74,7 @@ Cargo 期望源文件存放在 *src* 目录中。项目根目录只存放 README
|
||||
|
||||
现在让我们看看通过 Cargo 构建和运行 “Hello, world!” 程序有什么不同!在 *hello_cargo* 目录下,输入下面的命令来构建项目:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.85 secs
|
||||
@ -85,7 +82,7 @@ $ cargo build
|
||||
|
||||
这个命令会创建一个可执行文件 *target/debug/hello_cargo* (在 Windows 上是 *target\debug\hello_cargo.exe*),而不是放在目前目录下。可以通过这个命令运行可执行文件:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ ./target/debug/hello_cargo # 或者在 Windows 下为 .\target\debug\hello_cargo.exe
|
||||
Hello, world!
|
||||
```
|
||||
@ -94,7 +91,7 @@ Hello, world!
|
||||
|
||||
我们刚刚使用 `cargo build` 构建了项目,并使用 `./target/debug/hello_cargo` 运行了程序,也可以使用 `cargo run` 在一个命令中同时编译并运行生成的可执行文件:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||
Running `target/debug/hello_cargo`
|
||||
@ -103,7 +100,7 @@ Hello, world!
|
||||
|
||||
注意这一次并没有出现表明 Cargo 正在编译 `hello_cargo` 的输出。Cargo 发现文件并没有被改变,就直接运行了二进制文件。如果修改了源文件的话,Cargo 会在运行之前重新构建项目,并会出现像这样的输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.33 secs
|
||||
@ -113,7 +110,7 @@ Hello, world!
|
||||
|
||||
Cargo 还提供了一个叫 `cargo check` 的命令。该命令快速检查代码确保其可以编译,但并不产生可执行文件:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo check
|
||||
Checking hello_cargo v0.1.0 (file:///projects/hello_cargo)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs
|
||||
@ -123,8 +120,9 @@ $ cargo check
|
||||
|
||||
我们回顾下已学习的 Cargo 内容:
|
||||
|
||||
* 可以使用 `cargo build` 或 `cargo check` 构建项目。
|
||||
* 可以使用 `cargo build` 构建项目。
|
||||
* 可以使用 `cargo run` 一步构建并运行项目。
|
||||
* 可以在不生成二进制文件的情况下构建项目并使用 `cargo check` 检查错误。
|
||||
* 有别于将构建结果放在与源码相同的目录,Cargo 会将其放到 *target/debug* 目录。
|
||||
|
||||
使用 Cargo 的一个额外的优点是,不管你使用什么操作系统,其命令都是一样的。所以从现在开始本书将不再为 Linux 和 macOS 以及 Windows 提供相应的命令。
|
||||
@ -139,8 +137,8 @@ $ cargo check
|
||||
|
||||
即便 `hello_cargo` 项目十分简单,它现在也使用了很多在你之后的 Rust 生涯将会用到的实用工具。其实,要在任何已存在的项目上工作时,可以使用如下命令通过 Git 检出代码,移动到该项目目录并构建:
|
||||
|
||||
```text
|
||||
$ git clone someurl.com/someproject
|
||||
```console
|
||||
$ git clone example.org/someproject
|
||||
$ cd someproject
|
||||
$ cargo build
|
||||
```
|
||||
@ -162,3 +160,4 @@ $ cargo build
|
||||
是时候通过构建更实质性的程序来熟悉读写 Rust 代码了。所以在第二章我们会构建一个猜猜看游戏程序。如果你更愿意从学习 Rust 常用的编程概念开始,请阅读第三章,接着再回到第二章。
|
||||
|
||||
[installation]: ch01-01-installation.html#installation
|
||||
[appendix-e]: appendix-05-editions.html
|
@ -1,8 +1,8 @@
|
||||
# 编写 猜猜看 游戏
|
||||
|
||||
> [ch02-00-guessing-game-tutorial.md](https://github.com/rust-lang/book/blob/master/src/ch02-00-guessing-game-tutorial.md)
|
||||
> [ch02-00-guessing-game-tutorial.md](https://github.com/rust-lang/book/blob/main/src/ch02-00-guessing-game-tutorial.md)
|
||||
> <br>
|
||||
> commit c427a676393d001edc82f1a54a3b8026abcf9690
|
||||
> commit cba828634ec75fe6e61f34f3dd933ef1d78cffc6
|
||||
|
||||
让我们一起动手完成一个项目,来快速上手 Rust!本章将介绍 Rust 中一些常用概念,并通过真实的程序来展示如何运用它们。你将会学到 `let`、`match`、方法、关联函数、外部 crate 等知识!后续章节会深入探讨这些概念的细节。在这一章,我们将做基础练习。
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
要创建一个新项目,进入第一章中创建的 *projects* 目录,使用 Cargo 新建一个项目,如下:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo new guessing_game
|
||||
$ cd guessing_game
|
||||
```
|
||||
@ -27,14 +27,13 @@ $ cd guessing_game
|
||||
[package]
|
||||
name = "guessing_game"
|
||||
version = "0.1.0"
|
||||
authors = ["Your Name <you@example.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
```
|
||||
|
||||
如果 Cargo 从环境中获取的开发者信息不正确,修改这个文件并再次保存。
|
||||
|
||||
正如第一章那样,`cargo new` 生成了一个 “Hello, world!” 程序。查看 *src/main.rs* 文件:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
@ -47,10 +46,10 @@ fn main() {
|
||||
|
||||
现在使用 `cargo run` 命令,一步完成 “Hello, world!” 程序的编译和运行:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.50 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
|
||||
Running `target/debug/guessing_game`
|
||||
Hello, world!
|
||||
```
|
||||
@ -123,14 +122,14 @@ let mut guess = String::new();
|
||||
现在程序开始变得有意思了!这一小行代码发生了很多事。注意这是一个 `let` 语句,用来创建 **变量**(*variable*)。这里是另外一个例子:
|
||||
|
||||
```rust,ignore
|
||||
let foo = bar;
|
||||
let apples = 5;
|
||||
```
|
||||
|
||||
这行代码新建了一个叫做 `foo` 的变量并把它绑定到值 `bar` 上。在 Rust 中,变量默认是不可变的。我们将会在第三章的 [“变量与可变性”][variables-and-mutability] 部分详细讨论这个概念。下面的例子展示了如何在变量名前使用 `mut` 来使一个变量可变:
|
||||
这行代码新建了一个叫做 `apples` 的变量并把它绑定到值 `5` 上。在 Rust 中,变量默认是不可变的。我们将会在第三章的 [“变量与可变性”][variables-and-mutability] 部分详细讨论这个概念。下面的例子展示了如何在变量名前使用 `mut` 来使一个变量可变:
|
||||
|
||||
```rust,ignore
|
||||
let foo = 5; // 不可变
|
||||
let mut bar = 5; // 可变
|
||||
let apples = 5; // immutable
|
||||
let mut bananas = 5; // mutable
|
||||
```
|
||||
|
||||
> 注意:`//` 语法开始一个注释,持续到行尾。Rust 忽略注释中的所有内容,第三章将会详细介绍注释。
|
||||
@ -139,7 +138,7 @@ let mut bar = 5; // 可变
|
||||
|
||||
[string]: https://doc.rust-lang.org/std/string/struct.String.html
|
||||
|
||||
`::new` 那一行的 `::` 语法表明 `new` 是 `String` 类型的一个 **关联函数**(*associated function*)。关联函数是针对类型实现的,在这个例子中是 `String`,而不是 `String` 的某个特定实例。一些语言中把它称为 **静态方法**(*static method*)。
|
||||
`::new` 那一行的 `::` 语法表明 `new` 是 `String` 类型的一个 **关联函数**(*associated function*)。关联函数是针对类型实现的,在这个例子中是 `String`。
|
||||
|
||||
`new` 函数创建了一个新的空字符串,你会发现很多类型上有 `new` 函数,因为它是创建类型实例的惯用函数名。
|
||||
|
||||
@ -160,7 +159,7 @@ io::stdin().read_line(&mut guess)
|
||||
|
||||
[read_line]: https://doc.rust-lang.org/std/io/struct.Stdin.html#method.read_line
|
||||
|
||||
`read_line` 的工作是,无论用户在标准输入中键入什么内容,都将其存入一个字符串中,因此它需要字符串作为参数。这个字符串参数应该是可变的,以便 `read_line` 将用户输入附加上去。
|
||||
`read_line` 的工作是接收用户在标准输入中输入的任何内容,并将其追加到一个字符串中(不覆盖其内容),因此它将该字符串作为参数。这个字符串参数需要是可变的,以便该方法可以通过添加用户输入来改变字符串的内容。
|
||||
|
||||
`&` 表示这个参数是一个 **引用**(*reference*),它允许多处代码访问同一处数据,而无需在内存中多次拷贝。引用是一个复杂的特性,Rust 的一个主要优势就是安全而简单的操纵引用。完成当前程序并不需要了解如此多细节。现在,我们只需知道它像变量一样,默认是不可变的。因此,需要写成 `&mut guess` 来使其可变,而不是 `&guess`。(第四章会更全面的解释引用。)
|
||||
|
||||
@ -172,7 +171,7 @@ io::stdin().read_line(&mut guess)
|
||||
.expect("Failed to read line");
|
||||
```
|
||||
|
||||
当使用 `.foo()` 语法调用方法时,通过换行加缩进来把长行拆开是明智的。我们完全可以这样写:
|
||||
当使用 `.method_name()` 语法调用方法时,通过换行加缩进来把长行拆开是明智的。我们完全可以这样写:
|
||||
|
||||
```rust,ignore
|
||||
io::stdin().read_line(&mut guess).expect("Failed to read line");
|
||||
@ -197,16 +196,21 @@ io::stdin().read_line(&mut guess).expect("Failed to read line");
|
||||
|
||||
如果不调用 `expect`,程序也能编译,不过会出现一个警告:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
warning: unused `std::result::Result` which must be used
|
||||
warning: unused `Result` that must be used
|
||||
--> src/main.rs:10:5
|
||||
|
|
||||
10 | io::stdin().read_line(&mut guess);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: #[warn(unused_must_use)] on by default
|
||||
= note: `#[warn(unused_must_use)]` on by default
|
||||
= note: this `Result` may be an `Err` variant, which should be handled
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
|
||||
```
|
||||
|
||||
Rust 警告我们没有使用 `read_line` 的返回值 `Result`,说明有一个可能的错误没有处理。
|
||||
@ -215,7 +219,7 @@ Rust 警告我们没有使用 `read_line` 的返回值 `Result`,说明有一
|
||||
|
||||
### 使用 `println!` 占位符打印值
|
||||
|
||||
除了位于结尾的大括号,目前为止就只有这一行代码值得讨论一下了,就是这一行:
|
||||
除了位于结尾的右花括号,目前为止就只有这一行代码值得讨论一下了,就是这一行:
|
||||
|
||||
```rust,ignore
|
||||
println!("You guessed: {}", guess);
|
||||
@ -236,10 +240,10 @@ println!("x = {} and y = {}", x, y);
|
||||
|
||||
让我们来测试下猜猜看游戏的第一部分。使用 `cargo run` 运行:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
Please input your guess.
|
||||
@ -259,54 +263,58 @@ You guessed: 6
|
||||
|
||||
记住,*crate* 是一个 Rust 代码包。我们正在构建的项目是一个 **二进制 crate**,它生成一个可执行文件。 `rand` crate 是一个 **库 crate**,库 crate 可以包含任意能被其他程序使用的代码。
|
||||
|
||||
Cargo 对外部 crate 的运用是其真正闪光的地方。在我们使用 `rand` 编写代码之前,需要修改 *Cargo.toml* 文件,引入一个 `rand` 依赖。现在打开这个文件并在底部的 `[dependencies]` 片段标题之下添加:
|
||||
Cargo 对外部 crate 的运用是其真正的亮点所在。在我们使用 `rand` 编写代码之前,需要修改 *Cargo.toml* 文件,引入一个 `rand` 依赖。现在打开这个文件并将下面这一行添加到 `[dependencies]` 片段标题之下。请确保按照我们这里的方式指定 `rand`,否则本教程中的代码示例可能无法工作。
|
||||
|
||||
<span class="filename">文件名: Cargo.toml</span>
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
|
||||
rand = "0.5.5"
|
||||
rand = "0.8.3"
|
||||
```
|
||||
|
||||
在 *Cargo.toml* 文件中,标题以及之后的内容属同一个片段,直到遇到下一个标题才开始新的片段。`[dependencies]` 片段告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 `0.5.5` 来指定 `rand` crate。Cargo 理解[语义化版本(Semantic Versioning)][semver]<!-- ignore -->(有时也称为 *SemVer*),这是一种定义版本号的标准。`0.5.5` 事实上是 `^0.5.5` 的简写,它表示 “任何与 0.5.5 版本公有 API 相兼容的版本”。
|
||||
在 *Cargo.toml* 文件中,标题以及之后的内容属同一个片段,直到遇到下一个标题才开始新的片段。`[dependencies]` 片段告诉 Cargo 本项目依赖了哪些外部 crate 及其版本。本例中,我们使用语义化版本 `0.8.3` 来指定 `rand` crate。Cargo 理解[语义化版本(Semantic Versioning)][semver]<!-- ignore -->(有时也称为 *SemVer*),这是一种定义版本号的标准。`0.8.3` 事实上是 `^0.8.3` 的简写,它表示 “任何与 0.8.3 版本公有 API 相兼容的版本”。
|
||||
|
||||
[semver]: http://semver.org
|
||||
|
||||
现在,不修改任何代码,构建项目,如示例 2-2 所示:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Updating crates.io index
|
||||
Downloaded rand v0.5.5
|
||||
Downloaded libc v0.2.62
|
||||
Downloaded rand_core v0.2.2
|
||||
Downloaded rand_core v0.3.1
|
||||
Downloaded rand_core v0.4.2
|
||||
Compiling rand_core v0.4.2
|
||||
Compiling libc v0.2.62
|
||||
Compiling rand_core v0.3.1
|
||||
Compiling rand_core v0.2.2
|
||||
Compiling rand v0.5.5
|
||||
Downloaded rand v0.8.3
|
||||
Downloaded libc v0.2.86
|
||||
Downloaded getrandom v0.2.2
|
||||
Downloaded cfg-if v1.0.0
|
||||
Downloaded ppv-lite86 v0.2.10
|
||||
Downloaded rand_chacha v0.3.0
|
||||
Downloaded rand_core v0.6.2
|
||||
Compiling rand_core v0.6.2
|
||||
Compiling libc v0.2.86
|
||||
Compiling getrandom v0.2.2
|
||||
Compiling cfg-if v1.0.0
|
||||
Compiling ppv-lite86 v0.2.10
|
||||
Compiling rand_chacha v0.3.0
|
||||
Compiling rand v0.8.3
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53 s
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
|
||||
```
|
||||
|
||||
<span class="caption">示例 2-2: 将 rand crate 添加为依赖之后运行 `cargo build` 的输出</span>
|
||||
|
||||
可能会出现不同的版本号(多亏了语义化版本,它们与代码是兼容的!),同时显示顺序也可能会有所不同。
|
||||
可能会出现不同的版本号(多亏了语义化版本,它们与代码是兼容的!),不同的行(取决于操作系统),同时显示顺序也可能会有所不同。
|
||||
|
||||
现在我们有了一个外部依赖,Cargo 从 *registry* 上获取所有包的最新版本信息,这是一份来自 [Crates.io][cratesio] 的数据拷贝。Crates.io 是 Rust 生态环境中的开发者们向他人贡献 Rust 开源项目的地方。
|
||||
因为我们有了一个外部依赖,Cargo 从 *registry* 上获取所有包的最新版本信息,这是一份来自 [Crates.io][cratesio] 的数据拷贝。Crates.io 是 Rust 生态环境中的开发者们向他人贡献 Rust 开源项目的地方。
|
||||
|
||||
[cratesio]: https://crates.io
|
||||
|
||||
在更新完 registry 后,Cargo 检查 `[dependencies]` 片段并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 和 `rand_core` 的拷贝,因为 `rand` 依赖 `libc` 和 `rand_core` 来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
|
||||
在更新完 registry 后,Cargo 检查 `[dependencies]` 片段并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `rand` 所需要的其他 crates,因为 `rand` 依赖它们来正常工作。下载完成后,Rust 编译依赖,然后使用这些依赖编译项目。
|
||||
|
||||
如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 行之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。
|
||||
|
||||
如果打开 *src/main.rs* 文件,做一些无关紧要的修改,保存并再次构建,则会出现两行输出:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
|
||||
@ -316,30 +324,30 @@ $ cargo build
|
||||
|
||||
#### *Cargo.lock* 文件确保构建是可重现的
|
||||
|
||||
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.5.6` 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?
|
||||
Cargo 有一个机制来确保任何人在任何时候重新构建代码,都会产生相同的结果:Cargo 只会使用你指定的依赖版本,除非你又手动指定了别的。例如,如果下周 `rand` crate 的 `0.8.4` 版本出来了,它修复了一个重要的 bug,同时也含有一个会破坏代码运行的缺陷,这时会发生什么呢?
|
||||
|
||||
这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.5.5` 直到你显式升级,多亏有了 *Cargo.lock* 文件。
|
||||
这个问题的答案是 *Cargo.lock* 文件。它在第一次运行 `cargo build` 时创建,并放在 *guessing_game* 目录。当第一次构建项目时,Cargo 计算出所有符合要求的依赖版本并写入 *Cargo.lock* 文件。当将来构建项目时,Cargo 会发现 *Cargo.lock* 已存在并使用其中指定的版本,而不是再次计算所有的版本。这使得你拥有了一个自动化的可重现的构建。换句话说,项目会持续使用 `0.8.3` 直到你显式升级,多亏有了 *Cargo.lock* 文件。
|
||||
|
||||
#### 更新 crate 到一个新版本
|
||||
|
||||
当你 **确实** 需要升级 crate 时,Cargo 提供了另一个命令,`update`,它会忽略 *Cargo.lock* 文件,并计算出所有符合 *Cargo.toml* 声明的最新版本。如果成功了,Cargo 会把这些版本写入 *Cargo.lock* 文件。
|
||||
|
||||
不过,Cargo 默认只会寻找大于 `0.5.5` 而小于 `0.6.0` 的版本。如果 `rand` crate 发布了两个新版本,`0.5.6` 和 `0.6.0`,在运行 `cargo update` 时会出现如下内容:
|
||||
不过,Cargo 默认只会寻找大于 `0.8.3` 而小于 `0.9.0` 的版本。如果 `rand` crate 发布了两个新版本,`0.8.4` 和 `0.9.0`,在运行 `cargo update` 时会出现如下内容:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo update
|
||||
Updating crates.io index
|
||||
Updating rand v0.5.5 -> v0.5.6
|
||||
Updating rand v0.8.3 -> v0.8.4
|
||||
```
|
||||
|
||||
这时,你也会注意到的 *Cargo.lock* 文件中的变化无外乎现在使用的 `rand` crate 版本是`0.5.6`
|
||||
这时,你也会注意到的 *Cargo.lock* 文件中的变化无外乎现在使用的 `rand` crate 版本是`0.8.4`
|
||||
|
||||
如果想要使用 `0.6.0` 版本的 `rand` 或是任何 `0.6.x` 系列的版本,必须像这样更新 *Cargo.toml* 文件:
|
||||
如果想要使用 `0.9.0` 版本的 `rand` 或是任何 `0.9.x` 系列的版本,必须像这样更新 *Cargo.toml* 文件:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
|
||||
rand = "0.6.0"
|
||||
rand = "0.9.0"
|
||||
```
|
||||
|
||||
下一次运行 `cargo build` 时,Cargo 会从 registry 更新可用的 crate,并根据你指定的新版本重新计算。
|
||||
@ -362,7 +370,7 @@ use rand::Rng;
|
||||
fn main() {
|
||||
println!("Guess the number!");
|
||||
|
||||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||||
let secret_number = rand::thread_rng().gen_range(1..101);
|
||||
|
||||
println!("The secret number is: {}", secret_number);
|
||||
|
||||
@ -370,7 +378,8 @@ fn main() {
|
||||
|
||||
let mut guess = String::new();
|
||||
|
||||
io::stdin().read_line(&mut guess)
|
||||
io::stdin()
|
||||
.read_line(&mut guess)
|
||||
.expect("Failed to read line");
|
||||
|
||||
println!("You guessed: {}", guess);
|
||||
@ -381,7 +390,7 @@ fn main() {
|
||||
|
||||
首先,我们新增了一行 `use`:`use rand::Rng`。`Rng` 是一个 trait,它定义了随机数生成器应实现的方法,想使用这些方法的话,此 trait 必须在作用域中。第十章会详细介绍 trait。
|
||||
|
||||
接下来,我们在中间还新增加了两行。`rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接下来,调用随机数生成器的 `gen_range` 方法。这个方法由刚才引入到作用域的 `Rng` trait 定义。`gen_range` 方法获取两个数字作为参数,并生成一个范围在两者之间的随机数。它包含下限但不包含上限,所以需要指定 `1` 和 `101` 来请求一个 1 和 100 之间的数。
|
||||
接下来,我们在中间还新增加了两行。`rand::thread_rng` 函数提供实际使用的随机数生成器:它位于当前执行线程的本地环境中,并从操作系统获取 seed。接下来,调用随机数生成器的 `gen_range` 方法。这个方法由刚才引入到作用域的 `Rng` trait 定义。`gen_range` 方法接受一个范围表达式作为参数,并在该范围内生成一个随机数。我们在这里使用的范围表达式采用 `start..end` 形式,它包含下限但不包含上限,所以需要指定 `1..101` 来请求一个 1 和 100 之间的数。另外,我们也可以传递 `1..=100`,这是等价的。
|
||||
|
||||
> 注意:你不可能凭空就知道应该 use 哪个 trait 以及该从 crate 中调用哪个方法。crate 的使用说明位于其文档中。Cargo 有一个很棒的功能是:运行 `cargo doc --open` 命令来构建所有本地依赖提供的文档,并在浏览器中打开。例如,假设你对 `rand` crate 中的其他功能感兴趣,你可以运行 `cargo doc --open` 并点击左侧导航栏中的 `rand`。
|
||||
|
||||
@ -389,17 +398,19 @@ fn main() {
|
||||
|
||||
尝试运行程序几次:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 7
|
||||
Please input your guess.
|
||||
4
|
||||
You guessed: 4
|
||||
|
||||
$ cargo run
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 83
|
||||
@ -449,7 +460,7 @@ fn main() {
|
||||
|
||||
然而,示例 2-4 的代码并不能编译,可以尝试一下:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo build
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
error[E0308]: mismatched types
|
||||
@ -465,9 +476,9 @@ 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` 函数体中增加如下两行代码来实现:
|
||||
所以我们必须把从输入中读取到的 `String` 转换为一个真正的数字类型,才好与秘密数字进行比较。这可以通过在 `main` 函数体中增加另一行代码来实现:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -492,16 +503,15 @@ Could not compile `guessing_game`.
|
||||
}
|
||||
```
|
||||
|
||||
这两行新代码是:
|
||||
这行新代码是:
|
||||
|
||||
```rust,ignore
|
||||
let guess: u32 = guess.trim().parse()
|
||||
.expect("Please type a number!");
|
||||
let guess: u32 = guess.trim().parse().expect("Please type a number!");
|
||||
```
|
||||
|
||||
这里创建了一个叫做 `guess` 的变量。不过等等,不是已经有了一个叫做 `guess` 的变量了吗?确实如此,不过 Rust 允许用一个新值来 **隐藏** (*shadow*) `guess` 之前的值。这个功能常用在需要转换值类型之类的场景。它允许我们复用 `guess` 变量的名字,而不是被迫创建两个不同变量,诸如 `guess_str` 和 `guess` 之类。(第三章会介绍 shadowing 的更多细节。)
|
||||
|
||||
我们将 `guess` 绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符。`u32` 只能由数字字符转换,不过用户必须输入 <span class="keystroke">enter</span> 键才能让 `read_line` 返回,然而用户按下 <span class="keystroke">enter</span> 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 <span class="keystroke">5</span> 并按下 <span class="keystroke">enter</span>,`guess` 看起来像这样:`5\n`。`\n` 代表 “换行”,回车键。`trim` 方法消除 `\n`,只留下 `5`。
|
||||
我们将 `guess` 绑定到 `guess.trim().parse()` 表达式上。表达式中的 `guess` 是包含输入的原始 `String` 类型。`String` 实例的 `trim` 方法会去除字符串开头和结尾的空白字符。`u32` 只能由数字字符转换,不过用户必须输入 <span class="keystroke">enter</span> 键才能让 `read_line` 返回,然而用户按下 <span class="keystroke">enter</span> 键时,会在字符串中增加一个换行(newline)符。例如,用户输入 <span class="keystroke">5</span> 并按下 <span class="keystroke">enter</span>(在 Windows 上,按下 <span class="keystroke">enter</span> 键会得到一个回车符和一个换行符,`\r\n`),`guess` 看起来像这样:`5\n` 或者 `5\r\n`。`\n` 代表 “换行”,回车键;`\r` 代表 “回车”,回车键。`trim` 方法会消除 `\n` 或者 `\r\n`,只留下 `5`。
|
||||
|
||||
[字符串的 `parse` 方法][parse]<!-- ignore --> 将字符串解析成数字。因为这个方法可以解析多种数字类型,因此需要告诉 Rust 具体的数字类型,这里通过 `let guess: u32` 指定。`guess` 后面的冒号(`:`)告诉 Rust 我们指定了变量的类型。Rust 有一些内建的数字类型;`u32` 是一个无符号的 32 位整型。对于不大的正整数来说,它是不错的类型,第三章还会讲到其他数字类型。另外,程序中的 `u32` 注解以及与 `secret_number` 的比较,意味着 Rust 会推断出 `secret_number` 也是 `u32` 类型。现在可以使用相同类型比较两个值了!
|
||||
|
||||
@ -511,10 +521,10 @@ let guess: u32 = guess.trim().parse()
|
||||
|
||||
现在让我们运行程序!
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 58
|
||||
@ -555,12 +565,12 @@ Too big!
|
||||
|
||||
如上所示,我们将提示用户猜测之后的所有内容放入了循环。确保 loop 循环中的代码多缩进四个空格,再次运行程序。注意这里有一个新问题,因为程序忠实地执行了我们的要求:永远地请求另一个猜测,用户好像无法退出啊!
|
||||
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 [“比较猜测与秘密数字”](#comparing-the-guess-to-the-secret-number) 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
用户总能使用 <span class="keystroke">ctrl-c</span> 终止程序。不过还有另一个方法跳出无限循环,就是 [“比较猜测与秘密数字”](#比较猜测的数字和秘密数字) 部分提到的 `parse`:如果用户输入的答案不是一个数字,程序会崩溃。用户可以利用这一点来退出,如下所示:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.50 secs
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 59
|
||||
@ -578,9 +588,8 @@ You guessed: 59
|
||||
You win!
|
||||
Please input your guess.
|
||||
quit
|
||||
thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/libcore/result.rs:785
|
||||
note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
error: Process didn't exit successfully: `target/debug/guess` (exit code: 101)
|
||||
thread 'main' panicked at 'Please type a number!: ParseIntError { kind: InvalidDigit }', src/main.rs:28:47
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
```
|
||||
|
||||
输入 `quit` 确实退出了程序,同时其他任何非数字输入也一样。然而,这并不理想,我们想要当猜测正确的数字时游戏能自动退出。
|
||||
@ -640,9 +649,10 @@ println!("You guessed: {}", guess);
|
||||
|
||||
现在万事俱备,只需运行 `cargo run`:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 4.45s
|
||||
Running `target/debug/guessing_game`
|
||||
Guess the number!
|
||||
The secret number is: 61
|
||||
@ -674,7 +684,7 @@ use rand::Rng;
|
||||
fn main() {
|
||||
println!("Guess the number!");
|
||||
|
||||
let secret_number = rand::thread_rng().gen_range(1, 101);
|
||||
let secret_number = rand::thread_rng().gen_range(1..101);
|
||||
|
||||
loop {
|
||||
println!("Please input your guess.");
|
||||
@ -709,7 +719,7 @@ fn main() {
|
||||
|
||||
此时此刻,你顺利完成了猜猜看游戏。恭喜!
|
||||
|
||||
本项目通过动手实践,向你介绍了 Rust 新概念:`let`、`match`、方法、关联函数、使用外部 crate 等等,接下来的几章,你会继续深入学习这些概念。第三章介绍大部分编程语言都有的概念,比如变量、数据类型和函数,以及如何在 Rust 中使用它们。第四章探索所有权(ownership),这是一个 Rust 同其他语言大不相同的功能。第五章讨论结构体和方法的语法,而第六章侧重解释枚举。
|
||||
本项目通过动手实践,向你介绍了 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#变量和可变性
|
||||
|
@ -1,12 +1,14 @@
|
||||
# 常见编程概念
|
||||
|
||||
> [ch03-00-common-programming-concepts.md](https://github.com/rust-lang/book/blob/master/src/ch03-00-common-programming-concepts.md)
|
||||
> [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
|
@ -1,10 +1,10 @@
|
||||
## 变量和可变性
|
||||
|
||||
> [ch03-01-variables-and-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch03-01-variables-and-mutability.md)
|
||||
> [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,23 +120,27 @@ 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`,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不变的。
|
||||
隐藏与将变量标记为 `mut` 是有区别的。当不小心尝试对变量重新赋值时,如果没有使用 `let` 关键字,就会导致编译时错误。通过使用 `let`,我们可以用这个值进行一些计算,不过计算完之后变量仍然是不可变的。
|
||||
|
||||
`mut` 与隐藏的另一个区别是,当再次使用 `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
|
@ -1,8 +1,8 @@
|
||||
## 数据类型
|
||||
|
||||
> [ch03-02-data-types.md](https://github.com/rust-lang/book/blob/master/src/ch03-02-data-types.md)
|
||||
> [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
|
@ -1,10 +1,10 @@
|
||||
## 函数
|
||||
|
||||
> [ch03-03-how-functions-work.md](https://github.com/rust-lang/book/blob/master/src/ch03-03-how-functions-work.md)
|
||||
> [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 提供了一条信息,可能有助于纠正这个错误:它建议删除分号,这会修复这个错误。
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 注释
|
||||
|
||||
> [ch03-04-comments.md](https://github.com/rust-lang/book/blob/master/src/ch03-04-comments.md)
|
||||
> [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
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 控制流
|
||||
|
||||
> [ch03-05-control-flow.md](https://github.com/rust-lang/book/blob/master/src/ch03-05-control-flow.md)
|
||||
> [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,23 +194,26 @@ 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` 变量的类型是有效的。Rust 并不能够在 `number` 的类型只能在运行时确定的情况下工作;这样会使编译器变得更复杂而且只能为代码提供更少的保障,因为它不得不记录所有变量的多种可能的类型。
|
||||
`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#猜测正确后退出
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 认识所有权
|
||||
|
||||
> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/master/src/ch04-00-understanding-ownership.md)
|
||||
> [ch04-00-understanding-ownership.md](https://github.com/rust-lang/book/blob/main/src/ch04-00-understanding-ownership.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 什么是所有权?
|
||||
|
||||
> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/master/src/ch04-01-what-is-ownership.md)
|
||||
> [ch04-01-what-is-ownership.md](https://github.com/rust-lang/book/blob/main/src/ch04-01-what-is-ownership.md)
|
||||
> <br>
|
||||
> commit e81710c276b3839e8ec54d5f12aec4f9de88924b
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 引用与借用
|
||||
|
||||
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/master/src/ch04-02-references-and-borrowing.md)
|
||||
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/main/src/ch04-02-references-and-borrowing.md)
|
||||
> <br>
|
||||
> commit 4f19894e592cd24ac1476f1310dcf437ae83d4ba
|
||||
|
||||
@ -141,7 +141,7 @@ error[E0499]: cannot borrow `s` as mutable more than once at a time
|
||||
| -- first borrow later used here
|
||||
```
|
||||
|
||||
这个限制允许可变性,不过是以一种受限制的方式允许。新 Rustacean 们经常与此作斗争,因为大部分语言中变量任何时候都是可变的。
|
||||
这个限制允许可变性,不过是以一种受限制的方式允许。新 Rustacean 们经常难以适应这一点,因为大部分语言中变量任何时候都是可变的。
|
||||
|
||||
这个限制的好处是 Rust 可以在编译时就避免数据竞争。**数据竞争**(*data race*)类似于竞态条件,它可由这三个行为造成:
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Slice 类型
|
||||
|
||||
> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/master/src/ch04-03-slices.md)
|
||||
> [ch04-03-slices.md](https://github.com/rust-lang/book/blob/main/src/ch04-03-slices.md)
|
||||
> <br>
|
||||
> commit 9fcebe6e1b0b5e842285015dbf093f97cd5b3803
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 使用结构体组织相关联的数据
|
||||
|
||||
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-00-structs.md)
|
||||
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-00-structs.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 定义并实例化结构体
|
||||
|
||||
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-01-defining-structs.md)
|
||||
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-01-defining-structs.md)
|
||||
> <br>
|
||||
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 一个使用结构体的示例程序
|
||||
|
||||
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-02-example-structs.md)
|
||||
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/main/src/ch05-02-example-structs.md)
|
||||
> <br>
|
||||
> commit 9cb1d20394f047855a57228dc4cbbabd0a9b395a
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 方法语法
|
||||
|
||||
> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch05-03-method-syntax.md)
|
||||
> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch05-03-method-syntax.md)
|
||||
> <br>
|
||||
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 枚举和模式匹配
|
||||
|
||||
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/src/ch06-00-enums.md)
|
||||
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/main/src/ch06-00-enums.md)
|
||||
> <br>
|
||||
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 定义枚举
|
||||
|
||||
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/src/ch06-01-defining-an-enum.md)
|
||||
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/main/src/ch06-01-defining-an-enum.md)
|
||||
> <br>
|
||||
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
|
||||
|
||||
@ -101,7 +101,7 @@ let loopback = IpAddr::V6(String::from("::1"));
|
||||
|
||||
我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。
|
||||
|
||||
用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址存储为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举则可以轻易处理的这个情况:
|
||||
用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 `V4` 地址存储为四个 `u8` 值而 `V6` 地址仍然表现为一个 `String`,这就不能使用结构体了。枚举则可以轻易的处理这个情况:
|
||||
|
||||
```rust
|
||||
enum IpAddr {
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `match` 控制流运算符
|
||||
|
||||
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/src/ch06-02-match.md)
|
||||
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/main/src/ch06-02-match.md)
|
||||
> <br>
|
||||
> commit b374e75f1d7b743c84a6bb1ef72579a6588bcb8a
|
||||
|
||||
@ -8,7 +8,7 @@ Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我
|
||||
|
||||
可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
|
||||
|
||||
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示。
|
||||
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示。
|
||||
|
||||
```rust
|
||||
enum Coin {
|
||||
@ -65,10 +65,10 @@ fn value_in_cents(coin: Coin) -> u8 {
|
||||
|
||||
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。
|
||||
|
||||
作为一个例子,让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改:
|
||||
作为一个例子,让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美国在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的 `enum`,通过改变 `Quarter` 成员来包含一个 `State` 值,示例 6-4 中完成了这些修改:
|
||||
|
||||
```rust
|
||||
#[derive(Debug)] // 这样可以可以立刻看到州的名称
|
||||
#[derive(Debug)] // 这样可以立刻看到州的名称
|
||||
enum UsState {
|
||||
Alabama,
|
||||
Alaska,
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `if let` 简单控制流
|
||||
|
||||
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/src/ch06-03-if-let.md)
|
||||
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/main/src/ch06-03-if-let.md)
|
||||
> <br>
|
||||
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 使用包、Crate和模块管理不断增长的项目
|
||||
|
||||
> [ch07-00-managing-growing-projects-with-packages-crates-and-modules.md](https://github.com/rust-lang/book/blob/master/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md)
|
||||
> [ch07-00-managing-growing-projects-with-packages-crates-and-modules.md](https://github.com/rust-lang/book/blob/main/src/ch07-00-managing-growing-projects-with-packages-crates-and-modules.md)
|
||||
> <br>
|
||||
> commit 879fef2345bf32751a83a9e779e0cb84e79b6d3d
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## 包和 crate
|
||||
|
||||
> [ch07-01-packages-and-crates.md](https://github.com/rust-lang/book/blob/master/src/ch07-01-packages-and-crates.md)
|
||||
> [ch07-01-packages-and-crates.md](https://github.com/rust-lang/book/blob/main/src/ch07-01-packages-and-crates.md)
|
||||
> <br>
|
||||
> commit 879fef2345bf32751a83a9e779e0cb84e79b6d3d
|
||||
|
||||
模块系统的第一部分,我们将介绍包和 crate。crate 是一个二进制项或者库。*crate root* 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在 “[Defining Modules to Control Scope and Privacy](https://github.com/rust-lang/book/blob/master/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)” 一节深入解读)。*包*(*package*) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。
|
||||
模块系统的第一部分,我们将介绍包和 crate。crate 是一个二进制项或者库。*crate root* 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块(我们将在 “[定义模块来控制作用域与私有性](https://github.com/rust-lang/book/blob/master/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)” 一节深入解读)。*包*(*package*) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 *Cargo.toml* 文件,阐述如何去构建这些 crate。
|
||||
|
||||
包中所包含的内容由几条规则来确立。一个包中至多 **只能** 包含一个库 crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个 crate,无论是库的还是二进制的。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 定义模块来控制作用域与私有性
|
||||
|
||||
> [ch07-02-defining-modules-to-control-scope-and-privacy.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)
|
||||
> [ch07-02-defining-modules-to-control-scope-and-privacy.md](https://github.com/rust-lang/book/blob/main/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)
|
||||
> <br>
|
||||
> commit 34b089627cca09a73ce92a052222304bff0056e3
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 路径用于引用模块树中的项
|
||||
|
||||
> [ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md](https://github.com/rust-lang/book/blob/master/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md)
|
||||
> [ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md](https://github.com/rust-lang/book/blob/main/src/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.md)
|
||||
> <br>
|
||||
> commit cc6a1ef2614aa94003566027b285b249ccf961fa
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## 使用 `use` 关键字将名称引入作用域
|
||||
|
||||
> [ch07-04-bringing-paths-into-scope-with-the-use-keyword.md](https://github.com/rust-lang/book/blob/master/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md)
|
||||
> [ch07-04-bringing-paths-into-scope-with-the-use-keyword.md](https://github.com/rust-lang/book/blob/main/src/ch07-04-bringing-paths-into-scope-with-the-use-keyword.md)
|
||||
> <br>
|
||||
> commit 6d3e76820418f2d2bb203233c61d90390b5690f1
|
||||
|
||||
到目前为止,似乎我们编写的用于调用函数的路径都很冗长且重复,并不方便。例如,示例 7-7 中,无论我们选择 `add_to_waitlist` 函数的绝对路径还是相对路径,每次我们想要调用 `add_to_waitlist` 时,都必须指定`front_of_house` 和 `hosting`。幸运的是,有一种方法可以简化这个过程。我们可以一次性将路径引入作用域,然后使用 `use` 关键字调用该路径中的项,就如同它们是本地项一样。
|
||||
到目前为止,似乎我们编写的用于调用函数的路径都很冗长且重复,并不方便。例如,示例 7-7 中,无论我们选择 `add_to_waitlist` 函数的绝对路径还是相对路径,每次我们想要调用 `add_to_waitlist` 时,都必须指定`front_of_house` 和 `hosting`。幸运的是,有一种方法可以简化这个过程。我们可以使用 `use` 关键字将路径一次性引入作用域,然后调用该路径中的项,就如同它们是本地项一样。
|
||||
|
||||
在示例 7-11 中,我们将 `crate::front_of_house::hosting` 模块引入了 `eat_at_restaurant` 函数的作用域,而我们只需要指定 `hosting::add_to_waitlist` 即可在 `eat_at_restaurant` 中调用 `add_to_waitlist` 函数。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 将模块分割进不同文件
|
||||
|
||||
> [ch07-05-separating-modules-into-different-files.md](https://github.com/rust-lang/book/blob/master/src/ch07-05-separating-modules-into-different-files.md)
|
||||
> [ch07-05-separating-modules-into-different-files.md](https://github.com/rust-lang/book/blob/main/src/ch07-05-separating-modules-into-different-files.md)
|
||||
> <br>
|
||||
> commit a5a5bf9d6ea5763a9110f727911a21da854b1d90
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 常见集合
|
||||
|
||||
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/src/ch08-00-common-collections.md)
|
||||
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/main/src/ch08-00-common-collections.md)
|
||||
> <br>
|
||||
> commit 820ac357f6cf0e866e5a8e7a9c57dd3e17e9f8ca
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## vector 用来储存一系列的值
|
||||
|
||||
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/src/ch08-01-vectors.md)
|
||||
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/main/src/ch08-01-vectors.md)
|
||||
> <br>
|
||||
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用字符串存储 UTF-8 编码的文本
|
||||
|
||||
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/src/ch08-02-strings.md)
|
||||
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/main/src/ch08-02-strings.md)
|
||||
> <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
@ -132,7 +132,7 @@ fn add(self, s: &str) -> String {
|
||||
|
||||
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— 正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么示例 8-18 还能编译呢?
|
||||
|
||||
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
|
||||
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **Deref 强制转换**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
|
||||
|
||||
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 哈希 map 储存键值对
|
||||
|
||||
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/src/ch08-03-hash-maps.md)
|
||||
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/main/src/ch08-03-hash-maps.md)
|
||||
> <br>
|
||||
> commit 85b02530cc749565c26c05bf1b3a838334e9717f
|
||||
|
||||
@ -42,7 +42,7 @@ let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
|
||||
|
||||
<span class="caption">示例 8-21:用队伍列表和分数列表创建哈希 map</span>
|
||||
|
||||
这里 `HashMap<_, _>` 类型注解是必要的,因为可能 `collect` 很多不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。
|
||||
这里 `HashMap<_, _>` 类型注解是必要的,因为可能 `collect` 为很多不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。
|
||||
|
||||
### 哈希 map 和所有权
|
||||
|
||||
@ -186,7 +186,7 @@ println!("{:?}", map);
|
||||
|
||||
vector、字符串和哈希 map 会在你的程序需要储存、访问和修改数据时帮助你。这里有一些你应该能够解决的练习问题:
|
||||
|
||||
* 给定一系列数字,使用 vector 并返回这个列表的平均数(mean, average)、中位数(排列数组后位于中间的值)和众数(mode,出现次数最多的值;这里哈希函数会很有帮助)。
|
||||
* 给定一系列数字,使用 vector 并返回这个列表的平均数(mean, average)、中位数(排列数组后位于中间的值)和众数(mode,出现次数最多的值;这里哈希 map 会很有帮助)。
|
||||
* 将字符串转换为 Pig Latin,也就是每一个单词的第一个辅音字母被移动到单词的结尾并增加 “ay”,所以 “first” 会变成 “irst-fay”。元音字母开头的单词则在结尾增加 “hay”(“apple” 会变成 “apple-hay”)。牢记 UTF-8 编码!
|
||||
* 使用哈希 map 和 vector,创建一个文本接口来允许用户向公司的部门中增加员工的名字。例如,“Add Sally to Engineering” 或 “Add Amir to Sales”。接着让用户获取一个部门的所有员工的列表,或者公司每个部门的所有员工按照字典序排列的列表。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 错误处理
|
||||
|
||||
> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/src/ch09-00-error-handling.md)
|
||||
> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/main/src/ch09-00-error-handling.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `panic!` 与不可恢复的错误
|
||||
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-01-unrecoverable-errors-with-panic.md)
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/main/src/ch09-01-unrecoverable-errors-with-panic.md)
|
||||
> <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `Result` 与可恢复的错误
|
||||
|
||||
> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/master/src/ch09-02-recoverable-errors-with-result.md)
|
||||
> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/main/src/ch09-02-recoverable-errors-with-result.md)
|
||||
> <br>
|
||||
> commit aa339f78da31c330ede3f1b52b4bbfb62d7814cb
|
||||
|
||||
@ -180,7 +180,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
`expect` 与 `unwrap` 的使用方式一样:返回文件句柄或调用 `panic!` 宏。`expect` 用来调用 `panic!` 的错误信息将会作为参数传递给 `expect` ,而不像`unwrap` 那样使用默认的 `panic!` 信息。它看起来像这样:
|
||||
`expect` 与 `unwrap` 的使用方式一样:返回文件句柄或调用 `panic!` 宏。`expect` 在调用 `panic!` 时使用的错误信息将是我们传递给 `expect` 的参数,而不像 `unwrap` 那样使用默认的 `panic!` 信息。它看起来像这样:
|
||||
|
||||
```text
|
||||
thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `panic!` 还是不 `panic!`
|
||||
|
||||
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-03-to-panic-or-not-to-panic.md)
|
||||
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/main/src/ch09-03-to-panic-or-not-to-panic.md)
|
||||
> <br>
|
||||
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
|
||||
|
||||
@ -36,7 +36,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
|
||||
* 在此之后代码的运行依赖于不处于这种有害状态
|
||||
* 当没有可行的手段来将有害状态信息编码进所使用的类型中的情况
|
||||
|
||||
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,`panic!` 通常适合调用不能够控制的外部代码时,这时无法修复其返回的无效状态。
|
||||
如果别人调用你的代码并传递了一个没有意义的值,最好的情况也许就是 `panic!` 并警告使用你的库的人他的代码中有 bug 以便他能在开发时就修复它。类似的,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么 `panic!` 往往是合适的。
|
||||
|
||||
然而当错误预期会出现时,返回 `Result` 仍要比调用 `panic!` 更为合适。这样的例子包括解析器接收到格式错误的数据,或者 HTTP 请求返回了一个表明触发了限流的状态。在这些例子中,应该通过返回 `Result` 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 `panic!` 来处理这些情况就不是最好的选择。
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# 泛型、trait 和生命周期
|
||||
|
||||
> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/master/src/ch10-00-generics.md)
|
||||
> [ch10-00-generics.md](https://github.com/rust-lang/book/blob/main/src/ch10-00-generics.md)
|
||||
> <br>
|
||||
> commit 48b057106646758f6453f42b7887f34b8c24caf6
|
||||
|
||||
每一个编程语言都有高效处理重复概念的工具。在 Rust 中其工具之一就是 **泛型**(*generics*)。泛型是具体类型或其他属性的抽象替代。我们可以表达泛型的属性,比如他们的行为或如何与其他泛型相关联,而不需要在编写和编译代码时知道他们在这里实际上代表什么。
|
||||
|
||||
同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像 `i32` 或 `String` 这样的具体值。我们已经使用过第六章的 `Option<T>`,第八章的 `Vec<T>` 和 `HashMap<K, V>`,以及第九章的 `Result<T, E>` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法!
|
||||
同理为了编写一份可以用于多种具体值的代码,函数并不知道其参数为何值,这时就可以让函数获取泛型而不是像 `i32` 或 `String` 这样的具体类型。我们已经使用过第六章的 `Option<T>`,第八章的 `Vec<T>` 和 `HashMap<K, V>`,以及第九章的 `Result<T, E>` 这些泛型了。本章会探索如何使用泛型定义我们自己的类型、函数和方法!
|
||||
|
||||
首先,我们将回顾一下提取函数以减少代码重复的机制。接下来,我们将使用相同的技术,从两个仅参数类型不同的函数中创建一个泛型函数。我们也会讲到结构体和枚举定义中的泛型。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 泛型数据类型
|
||||
|
||||
> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch10-01-syntax.md)
|
||||
> [ch10-01-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch10-01-syntax.md)
|
||||
> <br>
|
||||
> commit af34ac954a6bd7fc4a8bbcc5c9685e23c5af87da
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
|
||||
### 在函数定义中使用泛型
|
||||
|
||||
当使用泛型定义函数时,我们在函数签名中通常为参数和返回值指定数据类型的位置放置泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。
|
||||
当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。
|
||||
|
||||
回到 `largest` 函数上,示例 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。
|
||||
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -54,21 +54,21 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 10-4:两个只在名称和签名中类型有所不同的函数</span>
|
||||
<span class="caption">示例 10-4:两个函数,不同点只是名称和签名类型</span>
|
||||
|
||||
`largest_i32` 函数是从示例 10-3 中提取的寻找 slice 中 `i32` 最大值的函数。`largest_char` 函数寻找 slice 中 `char` 的最大值:这两个函数有着相同的代码,所以让我们在一个单独的函数中引入泛型参数来消除重复。
|
||||
`largest_i32` 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 `i32`。`largest_char` 函数寻找 slice 中最大的 `char`。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。
|
||||
|
||||
为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。任何标识符都可以作为类型参数名。不过选择 `T` 是因为 Rust 的习惯是让变量名尽量短,通常就只有一个字母,同时 Rust 类型命名规范是骆驼命名法(CamelCase)。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
|
||||
为了参数化新函数中的这些类型,我们也需要为类型参数取个名字,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T`,因为传统上来说,Rust 的参数名字都比较短,通常就只有一个字母,同时,Rust 类型名的命名规范是骆驼命名法(CamelCase)。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
|
||||
|
||||
当需要在函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
|
||||
如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
|
||||
|
||||
```rust,ignore
|
||||
fn largest<T>(list: &[T]) -> T {
|
||||
```
|
||||
|
||||
这可以理解为:函数 `largest` 有泛型类型 `T`。它有一个参数 `list`,它的类型是一个 `T` 值的 slice。`largest` 函数将会返回一个与 `T` 相同类型的值。
|
||||
可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数的返回值类型也是 `T`。
|
||||
|
||||
示例 10-5 展示一个在签名中使用了泛型的统一的 `largest` 函数定义。该示例也向我们展示了如何对 `i32` 值的 slice 或 `char` 值的 slice 调用 `largest` 函数。注意这些代码还不能编译,不过本章稍后部分会修复错误。
|
||||
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -98,9 +98,9 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 10-5:一个还不能编译的使用泛型参数的 `largest` 函数定义</span>
|
||||
<span class="caption">示例 10-5:一个使用泛型参数的 `largest` 函数定义,尚不能编译</span>
|
||||
|
||||
如果现在就尝试编译这些代码,会出现如下错误:
|
||||
如果现在就编译这个代码,会出现如下错误:
|
||||
|
||||
```text
|
||||
error[E0369]: binary operation `>` cannot be applied to type `T`
|
||||
@ -118,7 +118,7 @@ error[E0369]: binary operation `>` cannot be applied to type `T`
|
||||
|
||||
### 结构体定义中的泛型
|
||||
|
||||
同样也可以使用 `<>` 语法来定义拥有一个或多个泛型参数类型字段的结构体。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x` 和 `y` 坐标值的结构体 `Point`:
|
||||
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x` 和 `y` 坐标值的结构体 `Point`:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -153,7 +153,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 10-7:字段 `x` 和 `y` 必须是相同类型,因为他们都有相同的泛型类型 `T`</span>
|
||||
<span class="caption">示例 10-7:字段 `x` 和 `y` 的类型必须相同,因为他们都有相同的泛型类型 `T`</span>
|
||||
|
||||
在这个例子中,当把整型值 5 赋值给 `x` 时,就告诉了编译器这个 `Point<T>` 实例中的泛型 `T` 是整型的。接着指定 `y` 为 4.0,它被定义为与 `x` 相同类型,就会得到一个像这样的类型不匹配错误:
|
||||
|
||||
@ -188,11 +188,11 @@ fn main() {
|
||||
|
||||
<span class="caption">示例 10-8:使用两个泛型的 `Point`,这样 `x` 和 `y` 可能是不同类型</span>
|
||||
|
||||
现在所有这些 `Point` 实例都是被允许的了!你可以在定义中使用任意多的泛型类型参数,不过太多的话代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重组为更小的部分。
|
||||
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重构,分解成更小的结构。
|
||||
|
||||
### 枚举定义中的泛型
|
||||
|
||||
类似于结构体,枚举也可以在其成员中存放泛型数据类型。第六章我们使用过了标准库提供的 `Option<T>` 枚举,让我们再看看:
|
||||
和结构体类似,枚举也可以在成员中存放泛型数据类型。第六章我们曾用过标准库提供的 `Option<T>` 枚举,这里再回顾一下:
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
@ -201,7 +201,7 @@ enum Option<T> {
|
||||
}
|
||||
```
|
||||
|
||||
现在这个定义看起来就更容易理解了。如你所见 `Option<T>` 是一个拥有泛型 `T` 的枚举,它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。通过 `Option<T>` 枚举可以表达有一个可能的值的抽象概念,同时因为 `Option<T>` 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
|
||||
现在这个定义应该更容易理解了。如你所见 `Option<T>` 是一个拥有泛型 `T` 的枚举,它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。通过 `Option<T>` 枚举可以表达有一个可能的值的抽象概念,同时因为 `Option<T>` 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
|
||||
|
||||
枚举也可以拥有多个泛型类型。第九章使用过的 `Result` 枚举定义就是一个这样的例子:
|
||||
|
||||
@ -212,15 +212,13 @@ enum Result<T, E> {
|
||||
}
|
||||
```
|
||||
|
||||
`Result` 枚举有两个泛型类型,`T` 和 `E`。`Result` 有两个成员:`Ok`,它存放一个类型 `T` 的值,而 `Err` 则存放一个类型 `E` 的值。这个定义使得 `Result` 枚举能很方便的表达任何可能成功(返回 `T` 类型的值)也可能失败(返回 `E` 类型的值)的操作。回忆一下示例 9-3 中打开一个文件的场景:当文件被成功打开 `T` 被放入了 `std::fs::File` 类型而当打开文件出现问题时 `E` 被放入了 `std::io::Error` 类型。
|
||||
`Result` 枚举有两个泛型类型,`T` 和 `E`。`Result` 有两个成员:`Ok`,它存放一个类型 `T` 的值,而 `Err` 则存放一个类型 `E` 的值。这个定义使得 `Result` 枚举能很方便的表达任何可能成功(返回 `T` 类型的值)也可能失败(返回 `E` 类型的值)的操作。实际上,这就是我们在示例 9-3 用来打开文件的方式:当成功打开文件的时候,`T` 对应的是 `std::fs::File` 类型;而当打开文件出现问题时,`E` 的值则是 `std::io::Error` 类型。
|
||||
|
||||
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复代码。
|
||||
当你意识到代码中定义了多个结构体或枚举,它们不一样的地方只是其中的值的类型的时候,不妨通过泛型类型来避免重复。
|
||||
|
||||
### 方法定义中的泛型
|
||||
|
||||
也可以在定义中使用泛型在结构体和枚举上实现方法(像第五章那样)。
|
||||
|
||||
可以像第五章介绍的那样来为其定义中带有泛型的结构体或枚举实现方法。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
|
||||
在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## trait:定义共享的行为
|
||||
|
||||
> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/master/src/ch10-02-traits.md)
|
||||
> [ch10-02-traits.md](https://github.com/rust-lang/book/blob/main/src/ch10-02-traits.md)
|
||||
> <br>
|
||||
> commit 34b403864ad9c5e27b00b7cc4a6893804ef5b989
|
||||
|
||||
@ -155,7 +155,7 @@ impl Summary for Tweet {
|
||||
}
|
||||
```
|
||||
|
||||
一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summary` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。
|
||||
一旦定义了 `summarize_author`,我们就可以对 `Tweet` 结构体的实例调用 `summarize` 了,而 `summarize` 的默认实现会调用我们提供的 `summarize_author` 定义。因为实现了 `summarize_author`,`Summary` trait 就提供了 `summarize` 方法的功能,且无需编写更多的代码。
|
||||
|
||||
```rust,ignore
|
||||
let tweet = Tweet {
|
||||
@ -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 的类型
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 生命周期与引用有效性
|
||||
|
||||
> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch10-03-lifetime-syntax.md)
|
||||
> [ch10-03-lifetime-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch10-03-lifetime-syntax.md)
|
||||
> <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 编写自动化测试
|
||||
|
||||
> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/master/src/ch11-00-testing.md)
|
||||
> [ch11-00-testing.md](https://github.com/rust-lang/book/blob/main/src/ch11-00-testing.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 如何编写测试
|
||||
|
||||
> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/master/src/ch11-01-writing-tests.md)
|
||||
> [ch11-01-writing-tests.md](https://github.com/rust-lang/book/blob/main/src/ch11-01-writing-tests.md)
|
||||
> <br>
|
||||
> commit cc6a1ef2614aa94003566027b285b249ccf961fa
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 控制测试如何运行
|
||||
|
||||
> [ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/master/src/ch11-02-running-tests.md)
|
||||
> [ch11-02-running-tests.md](https://github.com/rust-lang/book/blob/main/src/ch11-02-running-tests.md)
|
||||
> <br>
|
||||
> commit 42b802f26197f9a066e4a671d2b062af25972c13
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 测试的组织结构
|
||||
|
||||
> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/master/src/ch11-03-test-organization.md)
|
||||
> [ch11-03-test-organization.md](https://github.com/rust-lang/book/blob/main/src/ch11-03-test-organization.md)
|
||||
> <br>
|
||||
> commit 4badf9a8574c12794795b05954baf5adc579fa90
|
||||
|
||||
@ -30,7 +30,7 @@ mod tests {
|
||||
}
|
||||
```
|
||||
|
||||
上述代码就是自动生成的测试模块。`cfg` 属性代表 *configuration* ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性,Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。需要编译的不仅仅有标注为 `#[test]` 的函数之外,还包括测试模块中可能存在的帮助函数。
|
||||
上述代码就是自动生成的测试模块。`cfg` 属性代表 *configuration* ,它告诉 Rust 其之后的项只应该被包含进特定配置选项中。在这个例子中,配置选项是 `test`,即 Rust 所提供的用于编译和运行测试的配置选项。通过使用 `cfg` 属性,Cargo 只会在我们主动使用 `cargo test` 运行测试时才编译测试代码。这包括测试模块中可能存在的帮助函数, 以及标注为 #[test] 的函数。
|
||||
|
||||
#### 测试私有函数
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 一个 I/O 项目:构建一个命令行程序
|
||||
|
||||
> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch12-00-an-io-project.md)
|
||||
> [ch12-00-an-io-project.md](https://github.com/rust-lang/book/blob/main/src/ch12-00-an-io-project.md)
|
||||
> <br>
|
||||
> commit db919bc6bb9071566e9c4f05053672133eaac33e
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 接受命令行参数
|
||||
|
||||
> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/master/src/ch12-01-accepting-command-line-arguments.md)
|
||||
> [ch12-01-accepting-command-line-arguments.md](https://github.com/rust-lang/book/blob/main/src/ch12-01-accepting-command-line-arguments.md)
|
||||
> <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 读取文件
|
||||
|
||||
> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/master/src/ch12-02-reading-a-file.md)
|
||||
> [ch12-02-reading-a-file.md](https://github.com/rust-lang/book/blob/main/src/ch12-02-reading-a-file.md)
|
||||
> <br>
|
||||
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 重构改进模块性和错误处理
|
||||
|
||||
> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/master/src/ch12-03-improving-error-handling-and-modularity.md)
|
||||
> [ch12-03-improving-error-handling-and-modularity.md](https://github.com/rust-lang/book/blob/main/src/ch12-03-improving-error-handling-and-modularity.md)
|
||||
> <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
@ -116,7 +116,7 @@ fn parse_config(args: &[String]) -> Config {
|
||||
>
|
||||
> 由于其运行时消耗,许多 Rustacean 之间有一个趋势是倾向于避免使用 `clone` 来解决所有权问题。在关于迭代器的第十三章中,我们将会学习如何更有效率的处理这种情况,不过现在,复制一些字符串来取得进展是没有问题的,因为只会进行一次这样的拷贝,而且文件名和要搜索的字符串都比较短。在第一轮编写时拥有一个可以工作但有点低效的程序要比尝试过度优化代码更好一些。随着你对 Rust 更加熟练,将能更轻松的直奔合适的方法,不过现在调用 `clone` 是完全可以接受的。
|
||||
|
||||
我们更新 `main` 将 `parse_config` 返回的 `Config` 实例放入变量 `config` 中,并将之前分别使用 `search` 和 `filename` 变量的代码更新为现在的使用 `Config` 结构体的字段的代码。
|
||||
我们更新 `main` 将 `parse_config` 返回的 `Config` 实例放入变量 `config` 中,并将之前分别使用 `query` 和 `filename` 变量的代码更新为现在的使用 `Config` 结构体的字段的代码。
|
||||
|
||||
现在代码更明确的表现了我们的意图,`query` 和 `filename` 是相关联的并且他们的目的是配置程序如何工作。任何使用这些值的代码就知道在 `config` 实例中对应目的的字段名中寻找他们。
|
||||
|
||||
@ -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* 中定义这个函数:
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 采用测试驱动开发完善库的功能
|
||||
|
||||
> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/master/src/ch12-04-testing-the-librarys-functionality.md)
|
||||
> [ch12-04-testing-the-librarys-functionality.md](https://github.com/rust-lang/book/blob/main/src/ch12-04-testing-the-librarys-functionality.md)
|
||||
> <br>
|
||||
> commit 0ca4b88f75f8579de87adc2ad36d340709f5ccad
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 处理环境变量
|
||||
|
||||
> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/master/src/ch12-05-working-with-environment-variables.md)
|
||||
> [ch12-05-working-with-environment-variables.md](https://github.com/rust-lang/book/blob/main/src/ch12-05-working-with-environment-variables.md)
|
||||
> <br>
|
||||
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
## 将错误信息输出到标准错误而不是标准输出
|
||||
|
||||
> [ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/master/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
|
||||
> [ch12-06-writing-to-stderr-instead-of-stdout.md](https://github.com/rust-lang/book/blob/main/src/ch12-06-writing-to-stderr-instead-of-stdout.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
目前为止,我们将所有的输出都 `println!` 到了终端。大部分终端都提供了两种输出:**标准输出**(*standard output*,`stdout`)对应一般信息,**标准错误**(*standard error*,`stderr`)则用于错误信息。这种区别允许用户选择将程序正常输出定向到一个文件中并仍将错误信息打印到屏幕上。
|
||||
|
||||
但是 `println!` 函数只能够打印到标准输出,所以我们必需使用其他方法来打印到标准错误。
|
||||
但是 `println!` 函数只能够打印到标准输出,所以我们必须使用其他方法来打印到标准错误。
|
||||
|
||||
### 检查错误应该写入何处
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Rust 中的函数式语言功能:迭代器与闭包
|
||||
|
||||
> [ch13-00-functional-features.md](https://github.com/rust-lang/book/blob/master/src/ch13-00-functional-features.md)
|
||||
> [ch13-00-functional-features.md](https://github.com/rust-lang/book/blob/main/src/ch13-00-functional-features.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 闭包:可以捕获环境的匿名函数
|
||||
|
||||
> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/master/src/ch13-01-closures.md)
|
||||
> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/main/src/ch13-01-closures.md)
|
||||
> <br>
|
||||
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
|
||||
|
||||
@ -10,9 +10,9 @@ Rust 的 **闭包**(*closures*)是可以保存进变量或作为参数传递
|
||||
|
||||
让我们来看一个存储稍后要执行的闭包的示例。其间我们会讨论闭包的语法、类型推断和 trait。
|
||||
|
||||
考虑一下这个假想的情况:我们在一个通过 app 生成自定义健身计划的初创企业工作。其后端使用 Rust 编写,而生成健身计划的算法需要考虑很多不同的因素,比如用户的年龄、身体质量指数(Body Mass Index)、用户喜好、最近的健身活动和用户指定的强度系数。本例中实际的算法并不重要,重要的是这个计算只花费几秒钟。我们只希望在需要时调用算法,并且只希望调用一次,这样就不会让用户等得太久。
|
||||
考虑一下这个假定的场景:我们在一个通过 app 生成自定义健身计划的初创企业工作。其后端使用 Rust 编写,而生成健身计划的算法需要考虑很多不同的因素,比如用户的年龄、身体质量指数(Body Mass Index)、用户喜好、最近的健身活动和用户指定的强度系数。本例中实际的算法并不重要,重要的是这个计算只花费几秒钟。我们只希望在需要时调用算法,并且只希望调用一次,这样就不会让用户等得太久。
|
||||
|
||||
这里将通过调用 `simulated_expensive_calculation` 函数来模拟调用假象的算法,如示例 13-1 所示,它会打印出 `calculating slowly...`,等待两秒,并接着返回传递给它的数字:
|
||||
这里将通过调用 `simulated_expensive_calculation` 函数来模拟调用假定的算法,如示例 13-1 所示,它会打印出 `calculating slowly...`,等待两秒,并接着返回传递给它的数字:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -27,7 +27,7 @@ fn simulated_expensive_calculation(intensity: u32) -> u32 {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 13-1:一个用来代替假想计算的函数,它大约会执行两秒钟</span>
|
||||
<span class="caption">示例 13-1:一个用来代替假定计算的函数,它大约会执行两秒钟</span>
|
||||
|
||||
接下来,`main` 函数中将会包含本例的健身 app 中的重要部分。这代表当用户请求健身计划时 app 会调用的代码。因为与 app 前端的交互与闭包的使用并不相关,所以我们将硬编码代表程序输入的值并打印输出。
|
||||
|
||||
@ -219,7 +219,7 @@ fn generate_workout(intensity: u32, random_number: u32) {
|
||||
|
||||
<span class="caption">示例 13-6:调用定义的 `expensive_closure`</span>
|
||||
|
||||
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行改代码。
|
||||
现在耗时的计算只在一个地方被调用,并只会在需要结果的时候执行该代码。
|
||||
|
||||
然而,我们又重新引入了示例 13-3 中的问题:仍然在第一个 `if` 块中调用了闭包两次,这调用了慢计算代码两次而使得用户需要多等待一倍的时间。可以通过在 `if` 块中创建一个本地变量存放闭包调用的结果来解决这个问题,不过闭包可以提供另外一种解决方案。我们稍后会讨论这个方案,不过目前让我们首先讨论一下为何闭包定义中和所涉及的 trait 中没有类型注解。
|
||||
|
||||
@ -296,7 +296,7 @@ error[E0308]: mismatched types
|
||||
|
||||
为了让结构体存放闭包,我们需要指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
|
||||
|
||||
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在 [“闭包会捕获其环境”](#capturing-the-environment-with-closures) 部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
|
||||
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在 [“闭包会捕获其环境”](#闭包会捕获其环境) 部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
|
||||
|
||||
为了满足 `Fn` trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包有一个 `u32` 的参数并返回一个 `u32`,这样所指定的 trait bound 就是 `Fn(u32) -> u32`。
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## 使用迭代器处理元素序列
|
||||
|
||||
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/src/ch13-02-iterators.md)
|
||||
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md)
|
||||
> <br>
|
||||
> commit 8edf0457ab571b375b87357e1353ae0dd2127abe
|
||||
|
||||
迭代器模式允许你对一个项的序列进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
|
||||
迭代器模式允许你对一个序列的项进行某些处理。**迭代器**(*iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
|
||||
|
||||
在 Rust 中,迭代器是 **惰性的**(*lazy*),这意味着在调用方法使用迭代器之前它都不会有效果。例如,示例 13-13 中的代码通过调用定义于 `Vec` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身没有任何用处:
|
||||
|
||||
@ -50,7 +50,7 @@ pub trait Iterator {
|
||||
}
|
||||
```
|
||||
|
||||
注意这里有一下我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型**(*associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
|
||||
注意这里有一个我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型**(*associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
|
||||
|
||||
`next` 是 `Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 改进 I/O 项目
|
||||
|
||||
> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/src/ch13-03-improving-our-io-project.md)
|
||||
> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/main/src/ch13-03-improving-our-io-project.md)
|
||||
> <br>
|
||||
> commit 6555fb6c805fbfe7d0961980991f8bca6918928f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 性能对比:循环 VS 迭代器
|
||||
|
||||
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/src/ch13-04-performance.md)
|
||||
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/main/src/ch13-04-performance.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 进一步认识 Cargo 和 Crates.io
|
||||
|
||||
> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch14-00-more-about-cargo.md)
|
||||
> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/main/src/ch14-00-more-about-cargo.md)
|
||||
> <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 采用发布配置自定义构建
|
||||
|
||||
> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/master/src/ch14-01-release-profiles.md)
|
||||
> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/main/src/ch14-01-release-profiles.md)
|
||||
> <br>
|
||||
> commit 0f10093ac5fbd57feb2352e08ee6d3efd66f887c
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
## 将 crate 发布到 Crates.io
|
||||
|
||||
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/src/ch14-02-publishing-to-crates-io.md) <br>
|
||||
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/main/src/ch14-02-publishing-to-crates-io.md) <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向它人分享代码。[crates.io](https://crates.io)<!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。
|
||||
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向他人分享代码。[crates.io](https://crates.io)<!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。
|
||||
|
||||
Rust 和 Cargo 有一些帮助它人更方便找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
|
||||
Rust 和 Cargo 有一些帮助他人更方便找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
|
||||
|
||||
### 编写有用的文档注释
|
||||
|
||||
@ -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 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Cargo 工作空间
|
||||
|
||||
> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/master/src/ch14-03-cargo-workspaces.md)
|
||||
> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/main/src/ch14-03-cargo-workspaces.md)
|
||||
> <br>
|
||||
> commit 6d3e76820418f2d2bb203233c61d90390b5690f1
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用 `cargo install` 从 Crates.io 安装二进制文件
|
||||
|
||||
> [ch14-04-installing-binaries.md](https://github.com/rust-lang/book/blob/master/src/ch14-04-installing-binaries.md)
|
||||
> [ch14-04-installing-binaries.md](https://github.com/rust-lang/book/blob/main/src/ch14-04-installing-binaries.md)
|
||||
> <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Cargo 自定义扩展命令
|
||||
|
||||
> [ch14-05-extending-cargo.md](https://github.com/rust-lang/book/blob/master/src/ch14-05-extending-cargo.md)
|
||||
> [ch14-05-extending-cargo.md](https://github.com/rust-lang/book/blob/main/src/ch14-05-extending-cargo.md)
|
||||
> <br>
|
||||
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 智能指针
|
||||
|
||||
> [ch15-00-smart-pointers.md](https://github.com/rust-lang/book/blob/master/src/ch15-00-smart-pointers.md)
|
||||
> [ch15-00-smart-pointers.md](https://github.com/rust-lang/book/blob/main/src/ch15-00-smart-pointers.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用`Box <T>`指向堆上的数据
|
||||
|
||||
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/master/src/ch15-01-box.md) <br>
|
||||
> [ch15-01-box.md](https://github.com/rust-lang/book/blob/main/src/ch15-01-box.md) <br>
|
||||
> commit a203290c640a378453261948b3fee4c4c6eb3d0f
|
||||
|
||||
最简单直接的智能指针是 _box_,其类型是 `Box<T>`。 box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。如果你想回顾一下栈与堆的区别请参考第四章。
|
||||
@ -113,7 +113,7 @@ enum Message {
|
||||
}
|
||||
```
|
||||
|
||||
当 Rust 需要知道要为 `Message` 值分配多少空间时,它可以检查每一个成员并发现 `Message::Quit` 并不需要任何空间,`Message::Move` 需要足够储存两个 `i32` 值的空间,依此类推。因此,`Message` 值所需的空间等于储存其最大成员的空间大小。
|
||||
当 Rust 需要知道要为 `Message` 值分配多少空间时,它可以检查每一个成员并发现 `Message::Quit` 并不需要任何空间,`Message::Move` 需要足够储存两个 `i32` 值的空间,依此类推。因为 enum 实际上只会使用其中的一个成员,所以 `Message` 值所需的空间等于储存其最大成员的空间大小。
|
||||
|
||||
与此相对当 Rust 编译器检查像示例 15-2 中的 `List` 这样的递归类型时会发生什么呢。编译器尝试计算出储存一个 `List` 枚举需要多少内存,并开始检查 `Cons` 成员,那么 `Cons` 需要的空间等于 `i32` 的大小加上 `List` 的大小。为了计算 `List` 需要多少内存,它检查其成员,从 `Cons` 成员开始。`Cons`成员储存了一个 `i32` 值和一个`List`值,这样的计算将无限进行下去,如图 15-1 所示:
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
## 通过 `Deref` trait 将智能指针当作常规引用处理
|
||||
|
||||
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/master/src/ch15-02-deref.md) <br>
|
||||
> [ch15-02-deref.md](https://github.com/rust-lang/book/blob/main/src/ch15-02-deref.md) <br>
|
||||
> commit 44f1b71c117b0dcec7805eced0b95405167092f6
|
||||
|
||||
实现 `Deref` trait 允许我们重载 **解引用运算符**(_dereference operator_)`*`(与乘法运算符或通配符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
|
||||
|
||||
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态**(_deref coercions_)功能以及它是如何处理引用或智能指针的。
|
||||
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **Deref 强制转换**(_deref coercions_)功能以及它是如何处理引用或智能指针的。
|
||||
|
||||
> 我们将要构建的 `MyBox<T>` 类型与真正的 `Box<T>` 有一个很大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 `Deref`,所以其数据实际存放在何处,相比其类似指针的行为来说不算重要。
|
||||
|
||||
@ -154,13 +154,13 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行普通解引用
|
||||
|
||||
注意,每次当我们在代码中使用 `*` 时, `*` 运算符都被替换成了先调用 `deref` 方法再接着使用 `*` 解引用的操作,且只会发生一次,不会对 `*` 操作符无限递归替换,解引用出上面 `i32` 类型的值就停止了,这个值与示例 15-9 中 `assert_eq!` 的 `5` 相匹配。
|
||||
|
||||
### 函数和方法的隐式解引用强制多态
|
||||
### 函数和方法的隐式 Deref 强制转换
|
||||
|
||||
**解引用强制多态**(_deref coercions_)是 Rust 在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,解引用强制多态将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
**Deref 强制转换**(_deref coercions_)是 Rust 在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,Deref 强制转换将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
|
||||
|
||||
解引用强制多态的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&` 和 `*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
|
||||
Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&` 和 `*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
|
||||
|
||||
作为展示解引用强制多态的实例,让我们使用示例 15-8 中定义的 `MyBox<T>`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
|
||||
作为展示 Deref 强制转换的实例,让我们使用示例 15-8 中定义的 `MyBox<T>`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -172,7 +172,7 @@ fn hello(name: &str) {
|
||||
|
||||
<span class="caption">示例 15-11:`hello` 函数有着 `&str` 类型的参数 `name`</span>
|
||||
|
||||
可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`。解引用强制多态使得用 `MyBox<String>` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
|
||||
可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`。Deref 强制转换使得用 `MyBox<String>` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -205,11 +205,11 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-12:因为解引用强制多态,使用 `MyBox<String>` 的引用调用 `hello` 是可行的</span>
|
||||
<span class="caption">示例 15-12:因为 Deref 强制转换,使用 `MyBox<String>` 的引用调用 `hello` 是可行的</span>
|
||||
|
||||
这里使用 `&m` 调用 `hello` 函数,其为 `MyBox<String>` 值的引用。因为示例 15-10 中在 `MyBox<T>` 上实现了 `Deref` trait,Rust 可以通过 `deref` 调用将 `&MyBox<String>` 变为 `&String`。标准库中提供了 `String` 上的 `Deref` 实现,其会返回字符串 slice,这可以在 `Deref` 的 API 文档中看到。Rust 再次调用 `deref` 将 `&String` 变为 `&str`,这就符合 `hello` 函数的定义了。
|
||||
|
||||
如果 Rust 没有实现解引用强制多态,为了使用 `&MyBox<String>` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12:
|
||||
如果 Rust 没有实现 Deref 强制转换,为了使用 `&MyBox<String>` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -242,17 +242,17 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 15-13:如果 Rust 没有解引用强制多态则必须编写的代码</span>
|
||||
<span class="caption">示例 15-13:如果 Rust 没有 Deref 强制转换则必须编写的代码</span>
|
||||
|
||||
`(*m)` 将 `MyBox<String>` 解引用为 `String`。接着 `&` 和 `[..]` 获取了整个 `String` 的字符串 slice 来匹配 `hello` 的签名。没有解引用强制多态所有这些符号混在一起将更难以读写和理解。解引用强制多态使得 Rust 自动的帮我们处理这些转换。
|
||||
`(*m)` 将 `MyBox<String>` 解引用为 `String`。接着 `&` 和 `[..]` 获取了整个 `String` 的字符串 slice 来匹配 `hello` 的签名。没有 Deref 强制转换所有这些符号混在一起将更难以读写和理解。Deref 强制转换使得 Rust 自动的帮我们处理这些转换。
|
||||
|
||||
当所涉及到的类型定义了 `Deref` trait,Rust 会分析这些类型并使用任意多次 `Deref::deref` 调用以获得匹配参数的类型。这些解析都发生在编译时,所以利用解引用强制多态并没有运行时惩罚!
|
||||
当所涉及到的类型定义了 `Deref` trait,Rust 会分析这些类型并使用任意多次 `Deref::deref` 调用以获得匹配参数的类型。这些解析都发生在编译时,所以利用 Deref 强制转换并没有运行时惩罚!
|
||||
|
||||
### 解引用强制多态如何与可变性交互
|
||||
### Deref 强制转换如何与可变性交互
|
||||
|
||||
类似于如何使用 `Deref` trait 重载不可变引用的 `*` 运算符,Rust 提供了 `DerefMut` trait 用于重载可变引用的 `*` 运算符。
|
||||
|
||||
Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态:
|
||||
Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换:
|
||||
|
||||
- 当 `T: Deref<Target=U>` 时从 `&T` 到 `&U`。
|
||||
- 当 `T: DerefMut<Target=U>` 时从 `&mut T` 到 `&mut U`。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用 `Drop` Trait 运行清理代码
|
||||
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/master/src/ch15-03-drop.md) <br>
|
||||
> [ch15-03-drop.md](https://github.com/rust-lang/book/blob/main/src/ch15-03-drop.md) <br>
|
||||
> commit 57adb83f69a69e20862d9e107b2a8bab95169b4c
|
||||
|
||||
对于智能指针模式来说第二个重要的 trait 是 `Drop`,其允许我们在值要离开作用域时执行一些代码。可以为任何类型提供 `Drop` trait 的实现,同时所指定的代码被用于释放类似于文件或网络连接的资源。我们在智能指针上下文中讨论 `Drop` 是因为其功能几乎总是用于实现智能指针。例如,`Box<T>` 自定义了 `Drop` 用来释放 box 所指向的堆空间。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `Rc<T>` 引用计数智能指针
|
||||
|
||||
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/master/src/ch15-04-rc.md) <br>
|
||||
> [ch15-04-rc.md](https://github.com/rust-lang/book/blob/main/src/ch15-04-rc.md) <br>
|
||||
> commit 6f292c8439927b4c5b870dd4afd2bfc52cc4eccc
|
||||
|
||||
大部分情况下所有权是非常明确的:可以准确地知道哪个变量拥有某个值。然而,有些情况单个值可能会有多个所有者。例如,在图数据结构中,多个边可能指向相同的节点,而这个节点从概念上讲为所有指向它的边所拥有。节点直到没有任何边指向它之前都不应该被清理。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## `RefCell<T>` 和内部可变性模式
|
||||
|
||||
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/master/src/ch15-05-interior-mutability.md) <br>
|
||||
> [ch15-05-interior-mutability.md](https://github.com/rust-lang/book/blob/main/src/ch15-05-interior-mutability.md) <br>
|
||||
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
|
||||
|
||||
**内部可变性**(_Interior mutability_)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 `unsafe` 代码来模糊 Rust 通常的可变性和借用规则。我们还未讲到不安全代码;第十九章会学习它们。当可以确保代码在运行时会遵守借用规则,即使编译器不能保证的情况,可以选择使用那些运用内部可变性模式的类型。所涉及的 `unsafe` 代码将被封装进安全的 API 中,而外部类型仍然是不可变的。
|
||||
@ -112,7 +112,7 @@ impl<'a, T> LimitTracker<'a, T>
|
||||
|
||||
这些代码中一个重要部分是拥有一个方法 `send` 的 `Messenger` trait,其获取一个 `self` 的不可变引用和文本信息。这是我们的 mock 对象所需要拥有的接口。另一个重要的部分是我们需要测试 `LimitTracker` 的 `set_value` 方法的行为。可以改变传递的 `value` 参数的值,不过 `set_value` 并没有返回任何可供断言的值。也就是说,如果使用某个实现了 `Messenger` trait 的值和特定的 `max` 创建 `LimitTracker`,当传递不同 `value` 值时,消息发送者应被告知发送合适的消息。
|
||||
|
||||
我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象示例,用其创建 `LimitTracker`,调用 `LimitTracker` 的 `set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:
|
||||
我们所需的 mock 对象是,调用 `send` 并不实际发送 email 或消息,而是只记录信息被通知要发送了。可以新建一个 mock 对象实例,用其创建 `LimitTracker`,调用 `LimitTracker` 的 `set_value` 方法,然后检查 mock 对象是否有我们期望的消息。示例 15-21 展示了一个如此尝试的 mock 对象实现,不过借用检查器并不允许:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
@ -245,7 +245,7 @@ mod tests {
|
||||
|
||||
<span class="caption">示例 15-22:使用 `RefCell<T>` 能够在外部值被认为是不可变的情况下修改内部值</span>
|
||||
|
||||
现在 `sent_messages` 字段的类型是 `RefCell<Vec<String>>` 而不是 `Vec<String>`。在 `new` 函数中新建了一个 `RefCell` 示例替代空 vector。
|
||||
现在 `sent_messages` 字段的类型是 `RefCell<Vec<String>>` 而不是 `Vec<String>`。在 `new` 函数中新建了一个 `RefCell<Vec<String>>` 实例替代空 vector。
|
||||
|
||||
对于 `send` 方法的实现,第一个参数仍为 `self` 的不可变借用,这是符合方法定义的。我们调用 `self.sent_messages` 中 `RefCell` 的 `borrow_mut` 方法来获取 `RefCell` 中值的可变引用,这是一个 vector。接着可以对 vector 的可变引用调用 `push` 以便记录测试过程中看到的消息。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 引用循环与内存泄漏
|
||||
|
||||
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/master/src/ch15-06-reference-cycles.md) <br>
|
||||
> [ch15-06-reference-cycles.md](https://github.com/rust-lang/book/blob/main/src/ch15-06-reference-cycles.md) <br>
|
||||
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
|
||||
|
||||
Rust 的内存安全性保证使其难以意外地制造永远也不会被清理的内存(被称为 **内存泄漏**(_memory leak_)),但并不是不可能。与在编译时拒绝数据竞争不同, Rust 并不保证完全地避免内存泄漏,这意味着内存泄漏在 Rust 被认为是内存安全的。这一点可以通过 `Rc<T>` 和 `RefCell<T>` 看出:创建引用循环的可能性是存在的。这会造成内存泄漏,因为每一项的引用计数永远也到不了 0,其值也永远不会被丢弃。
|
||||
@ -37,7 +37,7 @@ impl List {
|
||||
|
||||
这里采用了示例 15-25 中 `List` 定义的另一种变体。现在 `Cons` 成员的第二个元素是 `RefCell<Rc<List>>`,这意味着不同于像示例 15-24 那样能够修改 `i32` 的值,我们希望能够修改 `Cons` 成员所指向的 `List`。这里还增加了一个 `tail` 方法来方便我们在有 `Cons` 成员的时候访问其第二项。
|
||||
|
||||
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `b` 中的列表指向 `a` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
|
||||
在示例 15-26 中增加了一个 `main` 函数,其使用了示例 15-25 中的定义。这些代码在 `a` 中创建了一个列表,一个指向 `a` 中列表的 `b` 列表,接着修改 `a` 中的列表指向 `b` 中的列表,这会创建一个引用循环。在这个过程的多个位置有 `println!` 语句展示引用计数。
|
||||
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 无畏并发
|
||||
|
||||
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/master/src/ch16-00-concurrency.md) <br>
|
||||
> [ch16-00-concurrency.md](https://github.com/rust-lang/book/blob/main/src/ch16-00-concurrency.md) <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
安全且高效的处理并发编程是 Rust 的另一个主要目标。**并发编程**(_Concurrent programming_),代表程序的不同部分相互独立的执行,而 **并行编程**(_parallel programming_)代表程序不同部分于同时执行,这两个概念随着计算机越来越多的利用多处理器的优势时显得愈发重要。由于历史原因,在此类上下文中编程一直是困难且容易出错的:Rust 希望能改变这一点。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用线程同时运行代码
|
||||
|
||||
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/master/src/ch16-01-threads.md) <br>
|
||||
> [ch16-01-threads.md](https://github.com/rust-lang/book/blob/main/src/ch16-01-threads.md) <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
在大部分现代操作系统中,已执行程序的代码在一个 **进程**(_process_)中运行,操作系统则负责管理多个进程。在程序内部,也可以拥有多个同时运行的独立部分。运行这些独立部分的功能被称为 **线程**(_threads_)。
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用消息传递在线程间传送数据
|
||||
|
||||
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/master/src/ch16-02-message-passing.md) <br>
|
||||
> [ch16-02-message-passing.md](https://github.com/rust-lang/book/blob/main/src/ch16-02-message-passing.md) <br>
|
||||
> commit 26565efc3f62d9dacb7c2c6d0f5974360e459493
|
||||
|
||||
一个日益流行的确保安全并发的方式是 **消息传递**(_message passing_),这里线程或 actor 通过发送包含数据的消息来相互沟通。这个思想来源于 [Go 编程语言文档中](http://golang.org/doc/effective_go.html) 的口号:“不要通过共享内存来通讯;而是通过通讯来共享内存。”(“Do not communicate by sharing memory; instead, share memory by communicating.”)
|
||||
@ -200,7 +200,7 @@ Got: thread
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
let tx1 = mpsc::Sender::clone(&tx);
|
||||
let tx1 = tx.clone();
|
||||
thread::spawn(move || {
|
||||
let vals = vec <br>
|
||||
> [ch16-03-shared-state.md](https://github.com/rust-lang/book/blob/main/src/ch16-03-shared-state.md) <br>
|
||||
> commit ef072458f903775e91ea9e21356154bc57ee31da
|
||||
|
||||
虽然消息传递是一个很好的处理并发的方式,但并不是唯一一个。再一次思考一下 Go 编程语言文档中口号的这一部分:“不要通过共享内存来通讯”(“do not communicate by sharing memory.”):
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 使用 `Sync` 和 `Send` trait 的可扩展并发
|
||||
|
||||
> [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/master/src/ch16-04-extensible-concurrency-sync-and-send.md) <br>
|
||||
> [ch16-04-extensible-concurrency-sync-and-send.md](https://github.com/rust-lang/book/blob/main/src/ch16-04-extensible-concurrency-sync-and-send.md) <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
Rust 的并发模型中一个有趣的方面是:语言本身对并发知之 **甚少**。我们之前讨论的几乎所有内容,都属于标准库,而不是语言本身的内容。由于不需要语言提供并发相关的基础设施,并发方案不受标准库或语言所限:我们可以编写自己的或使用别人编写的并发功能。
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Rust 的面向对象特性
|
||||
|
||||
> [ch17-00-oop.md](https://github.com/rust-lang/book/blob/master/src/ch17-00-oop.md)
|
||||
> [ch17-00-oop.md](https://github.com/rust-lang/book/blob/main/src/ch17-00-oop.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 面向对象语言的特征
|
||||
|
||||
> [ch17-01-what-is-oo.md](https://github.com/rust-lang/book/blob/master/src/ch17-01-what-is-oo.md)
|
||||
> [ch17-01-what-is-oo.md](https://github.com/rust-lang/book/blob/main/src/ch17-01-what-is-oo.md)
|
||||
> <br>
|
||||
> commit 34caca254c3e08ff9fe3ad985007f45e92577c03
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 为使用不同类型的值而设计的 trait 对象
|
||||
|
||||
> [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/master/src/ch17-02-trait-objects.md)
|
||||
> [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/main/src/ch17-02-trait-objects.md)
|
||||
> <br>
|
||||
> commit 7b23a000fc511d985069601eb5b09c6017e609eb
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 面向对象设计模式的实现
|
||||
|
||||
> [ch17-03-oo-design-patterns.md](https://github.com/rust-lang/book/blob/master/src/ch17-03-oo-design-patterns.md)
|
||||
> [ch17-03-oo-design-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch17-03-oo-design-patterns.md)
|
||||
> <br>
|
||||
> commit 7e219336581c41a80fd41f4fbe615fecb6ed0a7d
|
||||
|
||||
@ -286,7 +286,7 @@ impl Post {
|
||||
|
||||
接着调用 `unwrap` 方法,这里我们知道它永远也不会 panic,因为 `Post` 的所有方法都确保在他们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章 [“当我们比编译器知道更多的情况”][more-info-than-rustc] 部分讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况。
|
||||
|
||||
接着我们就有了一个 `&Box<State>`,当调用其 `content` 时,解引用强制多态会作用于 `&` 和 `Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 17-18 所示:
|
||||
接着我们就有了一个 `&Box<State>`,当调用其 `content` 时,Deref 强制转换会作用于 `&` 和 `Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 17-18 所示:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 模式用来匹配值的结构
|
||||
# 模式与模式匹配
|
||||
|
||||
> [ch18-00-patterns.md](https://github.com/rust-lang/book/blob/master/src/ch18-00-patterns.md)
|
||||
> [ch18-00-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch18-00-patterns.md)
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 所有可能会用到模式的位置
|
||||
|
||||
> [ch18-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/master/src/ch18-01-all-the-places-for-patterns.md)
|
||||
> [ch18-01-all-the-places-for-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch18-01-all-the-places-for-patterns.md)
|
||||
> <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
@ -112,7 +112,7 @@ c is at index 2
|
||||
|
||||
### `let` 语句
|
||||
|
||||
在本章之前,我们只明确的讨论过通过 `match` 和 `if let` 使用模式,不过事实上也在别地地方使用过模式,包括 `let` 语句。例如,考虑一下这个直白的 `let` 变量赋值:
|
||||
在本章之前,我们只明确的讨论过通过 `match` 和 `if let` 使用模式,不过事实上也在别的地方使用过模式,包括 `let` 语句。例如,考虑一下这个直白的 `let` 变量赋值:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
@ -124,7 +124,7 @@ let x = 5;
|
||||
let PATTERN = EXPRESSION;
|
||||
```
|
||||
|
||||
像 `let x = 5;` 这样的语句中变量名位于 `PATTERN` 位置,变量名不过是形式特别朴素的模式。我们将表达式与模式比较,并为任何找到的名称赋值。所以例如 `let x = 5;` 的情况,`x` 是一个模式代表 “将匹配到的值绑定到变量 x”。同时因为名称 `x` 是整个模式,这个模式实际上等于 “将任何值绑定到变量 `x`,不管值是什么”。
|
||||
像 `let x = 5;` 这样的语句中变量名位于 `PATTERN` 位置,变量名不过是形式特别朴素的模式。我们将表达式与模式比较,并为任何找到的名称赋值。所以例如 `let x = 5;` 的情况,`x` 是一个代表 “将匹配到的值绑定到变量 x” 的模式。同时因为名称 `x` 是整个模式,这个模式实际上等于 “将任何值绑定到变量 `x`,不管值是什么”。
|
||||
|
||||
为了更清楚的理解 `let` 的模式匹配方面的内容,考虑示例 18-4 中使用 `let` 和模式解构一个元组:
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## Refutability(可反驳性): 模式是否会匹配失效
|
||||
|
||||
> [ch18-02-refutability.md](https://github.com/rust-lang/book/blob/master/src/ch18-02-refutability.md)
|
||||
> [ch18-02-refutability.md](https://github.com/rust-lang/book/blob/main/src/ch18-02-refutability.md)
|
||||
> <br>
|
||||
> commit 30fe5484f3923617410032d28e86a5afdf4076fb
|
||||
|
||||
@ -18,7 +18,7 @@ let Some(x) = some_option_value;
|
||||
|
||||
<span class="caption">示例 18-8: 尝试在 `let` 中使用可反驳模式</span>
|
||||
|
||||
如果 `some_option_value` 的值是 `None`,其不会成功匹配模式 `Some(x)`,表明这个模式是可反驳的。然而 `let` 语句只能接受不可反驳模式因为代码不能通过 `None` 值进行有效的操作。Rust 会在编译时抱怨我们尝试在要求不可反驳模式的地方使用可反驳模式:
|
||||
如果 `some_option_value` 的值是 `None`,其不会成功匹配模式 `Some(x)`,表明这个模式是可反驳的。然而, 因为 `let` 对于 `None` 匹配不能产生任何任何合法的代码,所以 `let` 语句只能接受不可反驳模式。Rust 会在编译时抱怨我们尝试在要求不可反驳模式的地方使用可反驳模式:
|
||||
|
||||
```text
|
||||
error[E0005]: refutable pattern in local binding: `None` not covered
|
||||
@ -41,7 +41,7 @@ if let Some(x) = some_option_value {
|
||||
|
||||
<span class="caption">示例 18-9: 使用 `if let` 和一个带有可反驳模式的代码块来代替 `let`</span>
|
||||
|
||||
我们给了代码一个得以继续的出路!这段代码可以完美运行,尽管这意味着我们不能再使用不可反驳模式并免于收到错误。如果为 `if let` 提供了一个总是会匹配的模式,比如示例 18-10 中的 `x`,编译器会给出一个警告:
|
||||
我们给了代码一个得以继续的出路!虽然我们没办法在避免产生错误的情况下使用不可反驳模式,但这段使用可反驳模式的代码是完全有效的。如果为 `if let` 提供了一个总是会匹配的模式,比如示例 18-10 中的 `x`,编译器会给出一个警告:
|
||||
|
||||
```rust,ignore
|
||||
if let x = 5 {
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 所有的模式语法
|
||||
|
||||
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch18-03-pattern-syntax.md)
|
||||
> [ch18-03-pattern-syntax.md](https://github.com/rust-lang/book/blob/main/src/ch18-03-pattern-syntax.md)
|
||||
> <br>
|
||||
> commit 86f0ae4831f24b3c429fa4845b900b4cad903a8b
|
||||
|
||||
@ -54,7 +54,7 @@ fn main() {
|
||||
|
||||
一旦 `match` 表达式执行完毕,其作用域也就结束了,同理内部 `y` 的作用域也结束了。最后的 `println!` 会打印 `at the end: x = Some(5), y = 10`。
|
||||
|
||||
为了创建能够比较外部 `x` 和 `y` 的值,而不引入覆盖变量的 `match` 表达式,我们需要相应地使用带有条件的匹配守卫(match guard)。我们稍后将在 [“匹配守卫提供的额外条件”](#extra-conditionals-with-match-guards) 这一小节讨论匹配守卫。
|
||||
为了创建能够比较外部 `x` 和 `y` 的值,而不引入覆盖变量的 `match` 表达式,我们需要相应地使用带有条件的匹配守卫(match guard)。我们稍后将在 [“匹配守卫提供的额外条件”](#匹配守卫提供的额外条件) 这一小节讨论匹配守卫。
|
||||
|
||||
### 多个模式
|
||||
|
||||
@ -568,7 +568,7 @@ match x {
|
||||
|
||||
### `@` 绑定
|
||||
|
||||
*at* 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 `Message::Hello` 的 `id` 字段是否位于 `3...7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。
|
||||
*at* 运算符(`@`)允许我们在创建一个存放值的变量的同时测试其值是否匹配模式。示例 18-29 展示了一个例子,这里我们希望测试 `Message::Hello` 的 `id` 字段是否位于 `3..=7` 范围内,同时也希望能将其值绑定到 `id_variable` 变量中以便此分支相关联的代码可以使用它。可以将 `id_variable` 命名为 `id`,与字段同名,不过出于示例的目的这里选择了不同的名称。
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
@ -592,9 +592,9 @@ match msg {
|
||||
|
||||
<span class="caption">示例 18-29: 使用 `@` 在模式中绑定值的同时测试它</span>
|
||||
|
||||
上例会打印出 `Found an id in range: 5`。通过在 `3...7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
|
||||
上例会打印出 `Found an id in range: 5`。通过在 `3..=7` 之前指定 `id_variable @`,我们捕获了任何匹配此范围的值并同时测试其值匹配这个范围模式。
|
||||
|
||||
第二个分支只在模式中指定了一个范围,分支相关代码代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
|
||||
第二个分支只在模式中指定了一个范围,分支相关代码没有一个包含 `id` 字段实际值的变量。`id` 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 `id` 字段中的值,因为没有将 `id` 值保存进一个变量。
|
||||
|
||||
最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 `id`,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 `id` 字段的值进行测试:任何值都会匹配此分支。
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 高级特征
|
||||
|
||||
> [ch19-00-advanced-features.md](https://github.com/rust-lang/book/blob/master/src/ch19-00-advanced-features.md)
|
||||
> [ch19-00-advanced-features.md](https://github.com/rust-lang/book/blob/main/src/ch19-00-advanced-features.md)
|
||||
> <br>
|
||||
> commit 10f89936b02dc366a2d0b34083b97cadda9e0ce4
|
||||
|
||||
|
@ -1,18 +1,20 @@
|
||||
## 不安全 Rust
|
||||
|
||||
> [ch19-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/master/src/ch19-01-unsafe-rust.md)
|
||||
> [ch19-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/main/src/ch19-01-unsafe-rust.md)
|
||||
> <br>
|
||||
> commit 28fa3d15b0bc67ea5e79eeff2198e4277fc61baf
|
||||
> commit 4921fde29ae8ccf67d5893d4e43d74284626fded
|
||||
|
||||
目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 **不安全 Rust**(*unsafe Rust*)。它与常规 Rust 代码无异,但是会提供额外的超级力量。
|
||||
目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 **不安全 Rust**(*unsafe Rust*)。它与常规 Rust 代码无异,但是会提供额外的超能力。
|
||||
|
||||
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。这必然意味着有时代码可能是合法的,但是 Rust 不这么认为!在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
||||
尽管代码可能没问题,但如果 Rust 编译器没有足够的信息可以确定,它将拒绝代码。
|
||||
|
||||
不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些有效的程序比接受无效程序要好一些。这必然意味着有时代码 **可能** 是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。”这么做的缺点就是你只能靠自己了:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。
|
||||
|
||||
另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互,甚至于编写你自己的操作系统这样的底层系统编程!这也是 Rust 语言的目标之一。让我们看看不安全 Rust 能做什么,和怎么做。
|
||||
|
||||
### 不安全的超级力量
|
||||
### 不安全的超能力
|
||||
|
||||
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超级力量。” 这些超级力量是:
|
||||
可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超能力。” 这些超能力是:
|
||||
|
||||
* 解引用裸指针
|
||||
* 调用不安全的函数或方法
|
||||
@ -28,7 +30,7 @@
|
||||
|
||||
为了尽可能隔离不安全代码,将不安全代码封装进一个安全的抽象并提供安全 API 是一个好主意,当我们学习不安全函数和方法时会讨论到。标准库的一部分被实现为在被评审过的不安全代码之上的安全抽象。这个技术防止了 `unsafe` 泄露到所有你或者用户希望使用由 `unsafe` 代码实现的功能的地方,因为使用其安全抽象是安全的。
|
||||
|
||||
让我们按顺序依次介绍上述五个超级力量,同时我们会看到一些提供不安全代码的安全接口的抽象。
|
||||
让我们按顺序依次介绍上述五个超能力,同时我们会看到一些提供不安全代码的安全接口的抽象。
|
||||
|
||||
### 解引用裸指针
|
||||
|
||||
@ -69,7 +71,7 @@ let r = address as *const i32;
|
||||
|
||||
记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 19-3 所示:
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
let mut num = 5;
|
||||
|
||||
let r1 = &num as *const i32;
|
||||
@ -95,7 +97,7 @@ unsafe {
|
||||
|
||||
如下是一个没有做任何操作的不安全函数 `dangerous` 的例子:
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
unsafe fn dangerous() {}
|
||||
|
||||
unsafe {
|
||||
@ -105,12 +107,23 @@ unsafe {
|
||||
|
||||
必须在一个单独的 `unsafe` 块中调用 `dangerous` 函数。如果尝试不使用 `unsafe` 块调用 `dangerous`,则会得到一个错误:
|
||||
|
||||
```text
|
||||
error[E0133]: call to unsafe function requires unsafe function or block
|
||||
-->
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
|
||||
error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
|
||||
--> src/main.rs:4:5
|
||||
|
|
||||
4 | dangerous();
|
||||
| ^^^^^^^^^^^ call to unsafe function
|
||||
|
|
||||
= note: consult the function's documentation for information on how to avoid undefined behavior
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0133`.
|
||||
error: could not compile `unsafe-example`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
通过将 `dangerous` 调用插入 `unsafe` 块中,我们就向 Rust 保证了我们已经阅读过函数的文档,理解如何正确使用,并验证过其满足函数的契约。
|
||||
@ -155,23 +168,35 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
|
||||
如果尝试编译示例 19-5 的代码,会得到一个错误:
|
||||
|
||||
```text
|
||||
```console
|
||||
$ cargo run
|
||||
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example)
|
||||
error[E0499]: cannot borrow `*slice` as mutable more than once at a time
|
||||
-->
|
||||
--> src/main.rs:6:30
|
||||
|
|
||||
6 | (&mut slice[..mid],
|
||||
| ----- first mutable borrow occurs here
|
||||
7 | &mut slice[mid..])
|
||||
| ^^^^^ second mutable borrow occurs here
|
||||
8 | }
|
||||
| - first borrow ends here
|
||||
1 | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
| - let's call the lifetime of this reference `'1`
|
||||
...
|
||||
6 | (&mut slice[..mid], &mut slice[mid..])
|
||||
| -------------------------^^^^^--------
|
||||
| | | |
|
||||
| | | second mutable borrow occurs here
|
||||
| | first mutable borrow occurs here
|
||||
| returning this value requires that `*slice` is borrowed for `'1`
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0499`.
|
||||
error: could not compile `unsafe-example`
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了
|
||||
|
||||
示例 19-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`:
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
use std::slice;
|
||||
|
||||
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
@ -199,7 +224,7 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
|
||||
|
||||
与此相对,示例 19-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice:
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
use std::slice;
|
||||
|
||||
let address = 0x01234usize;
|
||||
@ -222,7 +247,7 @@ let slice: &[i32] = unsafe {
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
extern "C" {
|
||||
fn abs(input: i32) -> i32;
|
||||
}
|
||||
@ -271,7 +296,7 @@ fn main() {
|
||||
|
||||
<span class="caption">示例 19-9: 定义和使用一个不可变静态变量</span>
|
||||
|
||||
`static` 变量类似于第三章 [“变量和常量的区别”][differences-between-variables-and-constants] 部分讨论的常量。通常静态变量的名称采用 `SCREAMING_SNAKE_CASE` 写法,并 **必须** 标注变量的类型,在这个例子中是 `&'static str`。静态变量只能储存拥有 `'static` 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。
|
||||
静态(`static`)变量类似于第三章 [“变量和常量的区别”][differences-between-variables-and-constants] 部分讨论的常量。通常静态变量的名称采用 `SCREAMING_SNAKE_CASE` 写法。静态变量只能储存拥有 `'static` 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。
|
||||
|
||||
常量与不可变静态变量可能看起来很类似,不过一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。
|
||||
|
||||
@ -279,7 +304,7 @@ fn main() {
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,unsafe
|
||||
```rust
|
||||
static mut COUNTER: u32 = 0;
|
||||
|
||||
fn add_to_count(inc: u32) {
|
||||
@ -305,9 +330,9 @@ 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
|
||||
```rust
|
||||
unsafe trait Foo {
|
||||
// methods go here
|
||||
}
|
||||
@ -325,17 +350,17 @@ unsafe impl Foo for i32 {
|
||||
|
||||
### 访问联合体中的字段
|
||||
|
||||
`union` 和 `struct` 类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。可以查看[参考文档][reference]了解有关联合体的更多信息。
|
||||
仅适用于 `unsafe` 的最后一个操作是访问 **联合体** 中的字段,`union` 和 `struct` 类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。可以查看[参考文档][reference]了解有关联合体的更多信息。
|
||||
|
||||
### 何时使用不安全代码
|
||||
|
||||
使用 `unsafe` 来进行这五个操作(超级力量)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注使得在出现错误时易于追踪问题的源头。
|
||||
使用 `unsafe` 来进行这五个操作(超能力)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注可以更容易地在错误发生时追踪问题的源头。
|
||||
|
||||
[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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user