Merge pull request #2 from KaiserY/master

update merge pull
pull/218/head
Spartucus 6 years ago committed by GitHub
commit a2118e402d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

1
.gitignore vendored

@ -1,4 +1,5 @@
_book/ _book/
mdbook/ mdbook/
book/
.DS_Store .DS_Store

@ -8,12 +8,12 @@ branches:
only: only:
- master - master
before_script: before_script:
- (cargo install mdbook --vers 0.0.26 --force || true) - (cargo install mdbook --vers 0.1.7 --force || true)
script: script:
- mdbook build - mdbook build
deploy: deploy:
provider: pages provider: pages
local-dir: mdbook local-dir: book
skip-cleanup: true skip-cleanup: true
github-token: $GITHUB_TOKEN github-token: $GITHUB_TOKEN
keep-history: false keep-history: false

@ -1,6 +1,7 @@
[book]
title = "Rust 程序设计语言 简体中文版" title = "Rust 程序设计语言 简体中文版"
author = "Steve Klabnik 和 Carol Nichols以及来自 Rust 社区的贡献KaiserY 以及 Rust 中文社区翻译" author = "Steve Klabnik 和 Carol Nichols以及来自 Rust 社区的贡献KaiserY 以及 Rust 中文社区翻译"
description = "Rust 程序设计语言 简体中文版" description = "Rust 程序设计语言 简体中文版"
[output.html] [build-dir]
destination = "mdbook" destination = "mdbook"

@ -125,7 +125,7 @@
- [附录](appendix-00.md) - [附录](appendix-00.md)
- [A - 关键字](appendix-01-keywords.md) - [A - 关键字](appendix-01-keywords.md)
- [B - 运算符与符号](appendix-02-operators.md) - [B - 运算符与符号](appendix-02-operators.md)
- [C - 可导出的 trait](appendix-03-derivable-traits.md) - [C - 可派生的 trait](appendix-03-derivable-traits.md)
- [D - 宏](appendix-04-macros.md) - [D - 宏](appendix-04-macros.md)
- [E - 本书翻译](appendix-05-translation.md) - [E - 本书翻译](appendix-05-translation.md)
- [F - 最新功能](appendix-06-newest-features.md) - [F - 最新功能](appendix-06-newest-features.md)

@ -2,10 +2,11 @@
> [appendix-01-keywords.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-01-keywords.md) > [appendix-01-keywords.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-01-keywords.md)
> <br> > <br>
> commit 60f22d98fe8ee0ce04824a8536a95f12ba118027 > commit 32215c1d96c9046c0b553a05fa5ec3ede2e125c3
下面的列表中是Rust正在使用或者以后会用关键字。因此这些关键字不能被用作标识符例如 下面的列表中是Rust正在使用或者以后会用关键字。因此这些关键字不能被用作标识符例如
函数名、变量名、参数名、结构体、模块名、crates名、常量名、宏名、静态值的名字。 函数、变量、参数、结构体、模块、crate、常量、宏、静态值、属性、类型、trait 或生命周期
的名字。
### 目前正在使用的关键字 ### 目前正在使用的关键字
@ -39,12 +40,14 @@
* `super` - 表示当前模块的父模块 * `super` - 表示当前模块的父模块
* `trait` - 定义一个 **trait** * `trait` - 定义一个 **trait**
* `true` - 布尔值 `true` * `true` - 布尔值 `true`
* `type` - 定义一个类型别名或相关的类型 * `type` - 定义一个类型别名或相关的类型
* `unsafe` - 表示不安全的代码、函数、**traits** 或者方法实现 * `unsafe` - 表示不安全的代码、函数、**traits** 或者方法实现
* `use` - 引入外部空间的符号 * `use` - 引入外部空间的符号
* `where` - 表示一个类型约束 [\[For example\]](ch13-01-closures.html#使用带有泛型和-fn-trait-的闭包) * `where` - 表示一个类型约束 [\[For example\]][ch13-01]
* `while` - 基于一个表达式的结果判断是否进行循环 * `while` - 基于一个表达式的结果判断是否进行循环
[ch13-01]: ch13-01-closures.html#使用带有泛型和-fn-trait-的闭包
<!-- we should make sure the definitions for each keyword are consistently <!-- we should make sure the definitions for each keyword are consistently
phrased, so for example for enum we say "defining an enumeration" but for fn we phrased, so for example for enum we say "defining an enumeration" but for fn we
passively call it a "function definition" -- perhaps a good medium would be passively call it a "function definition" -- perhaps a good medium would be
@ -57,7 +60,7 @@ though. Please let me know if any still seem inconsistent /Carol -->
### 未使用的保留字 ### 未使用的保留字
这些关键字没有目前任何功能但是它们是Rust未来会使用的保留字。 这些关键字没有目前任何功能,但是它们是 Rust 未来会使用的保留字。
* `abstract` * `abstract`
* `alignof` * `alignof`

@ -1 +1,195 @@
# B - 运算符 ## 附录B - 运算符与符号
> [appendix-02-operators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-02-operators.md)
> <br />
> commit d50521fc08e51892cdf1edf5e35f3847a42f9432
[commit]: https://github.com/rust-lang/book/commit/d50521fc08e51892cdf1edf5e35f3847a42f9432
该附录包含了 Rust 语法的词汇表包括运算符以及其他的符号这些符号以其自身或者在路径、泛型、trait bounds、宏、属性、注释、元组以及大括号的上下文中出现。
### 运算符
表B-1包含了 Rust 中的运算符、运算符如何出现在上下文中的示例、简短解释以及该运算符是否可重载。如果一个运算符是可重载的,则该运算符上用于重载的相关 trait 也会列出。
<span class="caption">表 B-1: 运算符</span>
| 运算符 | 示例 | 解释 | 是否可重载 |
|----------|---------|-------------|---------------|
| `!` | `ident!(...)`, `ident!{...}`, `ident![...]` | 宏扩展 | |
| `!` | `!expr` | 按位非或逻辑非 | `Not` |
| `!=` | `var != expr` | 不等比较 | `PartialEq` |
| `%` | `expr % expr` | 算术取模 | `Rem` |
| `%=` | `var %= expr` | 算术取模与赋值 | `RemAssign` |
| `&` | `&expr`, `&mut expr` | 借用 | |
| `&` | `&type`, `&mut type`, `&'a type`, `&'a mut type` | 借用指针类型 | |
| `&` | `expr & expr` | 按位与 | `BitAnd` |
| `&=` | `var &= expr` | 按位与与及赋值 | `BitAndAssign` |
| `&&` | `expr && expr` | 逻辑与 | |
| `*` | `expr * expr` | 算术乘法 | `Mul` |
| `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` |
| `*` | `*expr` | 解引用 | |
| `*` | `*const type`, `*mut type` | 原生指针 | |
| `+` | `trait + trait`, `'a + trait` | 复合类型限制 | |
| `+` | `expr + expr` | 算术加法 | `Add` |
| `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` |
| `,` | `expr, expr` | 参数以及元素分隔符 | |
| `-` | `- expr` | 算术取负 | `Neg` |
| `-` | `expr - expr` | 算术减法| `Sub` |
| `-=` | `var -= expr` | 算术减法与赋值 | `SubAssign` |
| `->` | `fn(...) -> type`, <code>\|...\| -> type</code> | 函数与闭包,返回类型 | |
| `.` | `expr.ident` | 成员访问 | |
| `..` | `..`, `expr..`, `..expr`, `expr..expr` | 右排除范围 | |
| `..` | `..expr` | 结构体更新语法 | |
| `..` | `variant(x, ..)`, `struct_type { x, .. }` | “与剩余部分”的模式绑定 | |
| `...` | `expr...expr` | 模式: 范围包含模式 | |
| `/` | `expr / expr` | 算术除法 | `Div` |
| `/=` | `var /= expr` | 算术除法与赋值 | `DivAssign` |
| `:` | `pat: type`, `ident: type` | 约束 | |
| `:` | `ident: expr` | 结构体字段初始化 | |
| `:` | `'a: loop {...}` | 循环标志 | |
| `;` | `expr;` | 语句和语句结束符 | |
| `;` | `[...; len]` | 固定大小数组语法的部分 | |
| `<<` | `expr << expr` |左移 | `Shl` |
| `<<=` | `var <<= expr` | 左移与赋值| `ShlAssign` |
| `<` | `expr < expr` | 小于比较 | `PartialOrd` |
| `<=` | `expr <= expr` | 小于等于比较 | `PartialOrd` |
| `=` | `var = expr`, `ident = type` | 赋值/等值 | |
| `==` | `expr == expr` | 等于比较 | `PartialEq` |
| `=>` | `pat => expr` | 匹配准备语法的部分 | |
| `>` | `expr > expr` | 大于比较 | `PartialOrd` |
| `>=` | `expr >= expr` | 大于等于比较 | `PartialOrd` |
| `>>` | `expr >> expr` | 右移 | `Shr` |
| `>>=` | `var >>= expr` | 右移与赋值 | `ShrAssign` |
| `@` | `ident @ pat` | 模式绑定 | |
| `^` | `expr ^ expr` | 按位异或 | `BitXor` |
| `^=` | `var ^= expr` | 按位异或与赋值 | `BitXorAssign` |
| <code>\|</code> | <code>pat \| pat</code> | 模式选择 | |
| <code>\|</code> | <code>expr \| expr</code> | 按位或 | `BitOr` |
| <code>\|=</code> | <code>var \|= expr</code> | 按位或与赋值 | `BitOrAssign` |
| <code>\|\|</code> | <code>expr \|\| expr</code> | 逻辑或 | |
| `?` | `expr?` | 错误传播 | |
### 非运算符符号
下面的列表中包含了所有和运算符不一样功能的非字符符号;也就是说,他们并不像函数调用或方法调用一样表现。
表 B-2 展示了以其自身出现以及出现在合法其他各个地方的符号。
<span class="caption">表 B-2独立语法</span>
| 符号 | 解释 |
|--------|-------------|
| `'ident` | 命名生命周期或循环标签 |
| `...u8`, `...i32`, `...f64`, `...usize`, 等 | 指定类型的数值常量 |
| `"..."` | 字符串常量 |
| `r"..."`, `r#"..."#`, `r##"..."##`, etc. | 原生字符串常量, 未处理的遗漏字符 |
| `b"..."` | 字节字符串; 构造一个 `[u8]` 类型而非字符串 |
| `br"..."`, `br#"..."#`, `br##"..."##`, 等 | 原生字节字符串常量,原生字节和字节结合的字符串 |
| `'...'` | 字符常量 |
| `b'...'` | ASCII码字节常量 |
| <code>\|...\| expr</code> | 结束 |
| `!` | 对一个离散函数来说最后总是空类型 |
| `_` | “忽略”模式绑定, 也用于整数常量的可读性 |
<span class="caption">表 B-3 路径相关语法</span>
| 符号 | 解释 |
|--------|-------------|
| `ident::ident` | 命名空间路径 |
| `::path` | 与crate根相关的路径如一个明确的绝对路径 |
| `self::path` | 当前模块相关路径(如一个明确相关路径)|
| `super::path` | 父模块相关路径 |
| `type::ident`, `<type as trait>::ident` | 相关常量、函数以及类型 |
| `<type>::...` | 不可以被直接命名的相关项类型(如 `<&T>::...``<[T]>::...` 等) |
| `trait::method(...)` | 通过命名定义的 trait 来消除方法调用的二义性 |
| `type::method(...)` | 通过命名定义的类型来消除方法调用的二义性 |
| `<type as trait>::method(...)` | 通过命名 trait 和类型来消除方法调用的二义性 |
表 B-4 展示了出现在泛型类型参数上下文中的符号。
<span class="caption">表 B-4泛型</span>
| 符号 | 解释 |
|--------|-------------|
| `path<...>` | 为一个类型中的泛型指定具体参数(如 `Vec<u8>` |
| `path::<...>`, `method::<...>` | 为一个泛型、函数或表达式中的方法指定具体参数,通常指 [turbofish][turbofish] (如 `"42".parse::<i32>()`|
| `fn ident<...> ...` | 泛型函数定义 |
| `struct ident<...> ...` | 泛型结构体定义 |
| `enum ident<...> ...` | 泛型枚举定义 |
| `impl<...> ...` | 定义泛型实现 |
| `for<...> type` | 高级生命周期限制 |
| `type<ident=type>` | 泛型,其一个或多个相关类型必须被指定为特定类型(如 `Iterator<Item=T>`|
[turbofish]: https://matematikaadit.github.io/posts/rust-turbofish.html
表 B-5 展示了出现在使用 trait bounds 约束泛型参数上下文中的符号。
<span class="caption">表 B-5: Trait Bound 约束</span>
| 符号 | 解释 |
|--------|-------------|
| `T: U` | 泛型参数 `T` 约束于实现了 `U` 的类型 |
| `T: 'a` | 泛型 `T` 的生命周期必须长于 `'a`(意味着该类型不能传递包含生命周期短于 `'a` 的任何引用)|
| `T : 'static` | 泛型 `T` 包含了除 `'static` 之外的非借用引用 |
| `'b: 'a` | 泛型 `'b` 生命周期必须长于泛型 `'a` |
| `T: ?Sized` | 使用一个不定大小的泛型类型 |
| `'a + trait`, `trait + trait` | 复合类型限制 |
表 B-6 展示了在调用或定义宏以及在其上指定属性时的上下文中出现的符号。
<span class="caption">表 B-6: 宏与属性</span>
| 符号 | 解释 |
|--------|-------------|
| `#[meta]` | 外部属性 |
| `#![meta]` | 内部属性 |
| `$ident` | 宏替换 |
| `$ident:kind` | 宏捕获 |
| `$(…)…` | 宏重复 |
表 B-7 展示了写注释的符号。
<span class="caption">表 B-7: 注释</span>
| 符号 | 注释 |
|--------|-------------|
| `//` | 行注释 |
| `//!` | 内部行文档注释 |
| `///` | 外部行文档注释 |
| `/*...*/` | 块注释 |
| `/*!...*/` | 内部块文档注释 |
| `/**...*/` | 外部块文档注释 |
表 B-8 展示了出现在使用元组时上下文中的符号。
| 符号 | 解释 |
|--------|-------------|
| `()` | 空元祖(亦称单元), 用于常量或类型中 |
| `(expr)` | 括号表达式 |
| `(expr,)` | 单一元素元组表达式 |
| `(type,)` | 单一元素元组类型 |
| `(expr, ...)` | 元组表达式 |
| `(type, ...)` | 元组类型 |
| `expr(expr, ...)` | 函数调用表达式; 也用于初始化元组结构体 `struct` 以及元组枚举 `enum` 变体 |
| `ident!(...)`, `ident!{...}`, `ident![...]` | 宏调用 |
| `expr.0`, `expr.1`, etc. | 元组索引 |
表 B-9 使用大括号的符号。
| 符号 | 解释 |
|---------|-------------|
| `{...}` | 块表达式 |
| `Type {...}` | `struct` |
表 B-10 展示了使用方括号的符号。
<span class="caption">表 B-10: 方括号</span>
| 符号 | 解释 |
|---------|-------------|
| `[...]` | 数组 |
| `[expr; len]` | 复制了 `len``expr`的数组 |
| `[type; len]` | 包含 `len``type` 类型的数组|
| `expr[expr]` | 集合索引。 重载(`Index`, `IndexMut` |
| `expr[..]`, `expr[a..]`, `expr[..b]`, `expr[a..b]` | 集合索引,使用 `Range``RangeFrom``RangeTo` 或 `RangeFull` 作为索引来代替集合切片 |

@ -1 +1,92 @@
# C - 可导出的 trait # 附录C - 可派生的 trait
> [appendix-03-derivable-traits.md][appendix-03]
> <br />
> commit 32215c1d96c9046c0b553a05fa5ec3ede2e125c3
[appendix-03]: https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-03-derivable-traits.md
[commit]: https://github.com/rust-lang/book/commit/32215c1d96c9046c0b553a05fa5ec3ede2e125c3
在本书的各个部分中,我们讨论了可应用于结构体和枚举的 `derive` 属性。`derive` 属性生成的代码在使用 `derive` 语法注释的类型之上实现了带有默认实现的 trait 。
在该附录中,我们提供标准库中所有可以使用 `derive` 的 trait 的参考。每部分都包含:
* 该 trait 将会派生什么样的操作符和方法
* 由 `derive` 提供什么样的 trait 实现
* 由什么来实现类型的 trait
* 是否允许实现该 trait 的条件
* 需要 trait 操作的例子
`derive` 属性提供的行为相比如果你需要与之不同的行为,请查阅标准库文档以获取每个 trait 的详情,来手动实现它们。
在类型上无法使用 `derive` 实现标准库的其余 trait。这些 trait 没有合理的默认行为, 因此,你可以以一种尝试完成的合理方式实现它们。
一个无法被派生的 trait 的例子是为最终用户处理格式化的 `Display` 。你应该时常考虑使用合适的方法来为最终用户显示一个类型。最终用户应该看到类型的什么部分他们会找出相关部分吗对他们来说最相互关联的数据格式是什么样的Rust 编译器没有这样的洞察力,因此,无法为你提供合适的默认行为。
本附录所提供的可派生 trait 列表并不全面:库可以为它们自己的 trait 实现 `derive` , 让可以使用 `derive` 的 trait 列表真诚的开放。实现 `derive` 涉及使用程序化宏这在附录D中有介绍。
## 编程人员输出的 `Debug`
`Debug` trait 在格式化字符串中使调试格式化,你可以在 `{}` 占位符里面加上 `:?` 显示它。
`Debug` trait 允许你以调试目的来打印一个类型的实例,因此,使用类型的你以及其他的编程人员可以让程序在执行时在指定点上显示一个实例。
例如,在使用 `assert_eq!` 宏时Debug` trait 是必须的。如果等式断言失败,这个宏就把给定实例的值作为参数打印出来,因此,编程人员可以看到两个实例为什么不相等。
## 等值比较的 `PartialEq``Eq`
`PartialEq` trait 可以比较一个类型的实例以检查是否相等,并且可以使用 `==``!=` 操作符。
派生的 `PartialEq` 实现了 `eq` 方法。当 `PartialEq` 在结构体上派生时,只有*所有*的字段都相等时两个实例才相等,同时只要有字段不相等则两个实例就不相等。当在枚举上派生时,每一个变体(variant)都和它自身相等,且和其他变体都不相等。
例如,当使用 `assert_eq!` 宏时,需要比较比较一个类型的两个实例是否相等,则 `PartialEq` trait 是必须的。
`Eq` trait 没有方法。其目的是为每一个注解类型的值作标志,其值等于其自身。 `Eq` trait 只能应用于那些实现了 `PartialEq` 的类型,但并非所有实现了 `PartialEq` 的类型可以实现 `Eq`。浮点类型就是一个例子:浮点数状态的实现,两个非数字(`NaN`not-a-number值是互不相等的。
例如,对于一个 `HashMap<K, V>` 中的 key 来说, `Eq` 是必须的,这样 `HashMap<K, V>` 就可以知道两个 key 是否一样了。
## 次序比较的 `PartialOrd``Ord`
`PartialOrd` trait 可以基于排序的目的而比较一个类型的实例。实现了 `PartialOrd` 的类型可以使用 `<``>`、`<=` 和 `>=` 操作符。但只能在同时实现了 `PartialEq` 的类型上使用 `PartialOrd`
派生 `PartialOrd` 实现了 `partial_cmp` 方法,其返回一个 `Option<Ordering>` ,但当给定值无法产生顺序时将返回 `None`。尽管大多数类型的值都可以比较,但一个无法产生顺序的例子是:浮点类型的非数字值(`NaN`not-a-number。当在浮点数上调用 `partial_cmp` 时,`NaN` 的浮点数将返回 `None`
当在结构体上派生时,`PartialOrd` 以在结构体定义中字段出现的顺序比较每个字段的值来比较两个实例。当在枚举上派生时,认为在枚举定义中声明较早的枚举变体小于其后的变体。
例如,对于来自于 `rand` carte 中的 `gen_range` 方法来说,当在一个大值和小值指定的范围内生成一个随机值时,`PartialOrd` trait 是必须的。
`Ord` trait 也让你明白在一个带注解类型上的任意两个值存在有效顺序。`Ord` trait 实现了 `cmp` 方法,它返回一个 `Ordering` 而不是 `Option<Ordering>`,因为总存在一个合法的顺序。只可以在实现了 `PartialOrd``Eq` `Eq` 依赖 `PartialEq` )的类型上使用 `Ord` trait 。当在结构体或枚举上派生时, `cmp` 和以 `PartialOrd` 派生实现的 `partial_cmp` 表现一致。
例如,当在 `BTreeSet<T>` (一种基于有序值存储数据的数据结构)上存值时,`Ord` 是必须的。
## 复制值的 `Clone``Copy`
`Clone` trait 可以明确地创建一个值的深拷贝( deep copy ),复制过程可能包含任意代码的执行以及堆上数据的复制。查阅第四章“变量和数据的交互方式:移动”以获取有关 `Clone` 的更多信息。
派生 `Clone` 实现了 `clone` 方法,其为整个的类型实现时,在类型的每一部分上调用了 `clone` 方法。这意味着类型中所有字段或值也必须实现 `Clone` 来派生 `Clone`
例如,当在一个切片上调用 `to_vec` 方法时,`Clone` 是必须的。切片并不拥有其所包含实例的类型,但是从 `to_vec` 中返回的 vector 需要拥有其实例,因此,`to_vec` 在每个元素上调用 `clone`。因此,存储在切片中的类型必须实现 `Clone`
`Copy` trait 允许你通过只拷贝存储在栈上的位来复制值而不需要其他代码。查阅第四章“只在栈上的数据:拷贝”的部分来获取有关 `Copy` 的更多信息。
`Copy` trait 并未定义任何方法来阻止编程人员重写这些方法或违反无代码可执行的假设。所以,所有的编程人员可以假设复制一个值非常快。
可以在类型内部全部实现 `Copy` trait 的任意类型上派生 `Copy`。 但只可以在那些同时实现了 `Clone` 的类型上使用 `Copy` trait ,因为一个实现 `Copy` 的类型在尝试实现 `Clone` 时执行和 `Copy` 相同的任务。
`Copy` trait 很少使用;实现 `Copy` 的类型是可以优化的,这意味着你无需调用 `clone`,这让代码更简洁。
使用 `Clone` 实现 `Copy` 也是有可能的,但代码可能会稍慢或是要使用 `clone` 替代。
## 固定大小的值到值映射的 `Hash`
`Hash` trait 可以实例化一个任意大小的类型并且能够用哈希hash函数将该实例映射到一个固定大小的值上。派生 `Hash` 实现了 `hash` 方法。`hash` 方法的派生实现结合了在类型的每部分调用 `hash` 的结果,这意味着所有的字段或值也必须将 `Hash` 实现为派生 `Hash`
例如,在 `HashMap<K, V>` 的 key 上存储有效数据时,`Hash` 是必须的。
## 默认值的 `Default`
`Default` trait 使你创建一个类型的默认值。 派生 `Default` 实现了 `default` 函数。`default` 函数的派生实现调用了类型每部分的 `default` 函数,这意味着类型中所有的字段或值也必须把 `Default` 实现为派生 `Default`
`Default::default` 函数通常结合结构体更新语法一起使用,这在第五章的“使用结构体更新语法从其他实例中创建实例”部分有讨论。可以自定义一个结构体的一小部分字段而剩余字段则使用 `..Default::default()` 设置为默认值。
例如,当你在 `Option<T>` 实例上使用 `unwrap_or_default` 方法时,`Default` trait是必须的。如果 `Option<T>``None`的话, `unwrap_or_default` 方法将返回存储在 `Option<T>``T` 类型的 `Default::default` 的结果。

@ -1 +1,299 @@
# D - 宏 ## 附录D - 宏
> [appendix-04-macros.md][appendix-04]
> <br>
> commit 32215c1d96c9046c0b553a05fa5ec3ede2e125c3
[appendix-04]: https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-04-macros.md
我们已经在本书中像 *println!* 这样使用过宏了,但还没完全探索什么是宏以及它是如何工作的。本附录以如下方式解释宏:
* 什么是宏以及与函数有何区别
* 如何定义一个声明式宏( declarative macro 来进行元编程metaprogramming
* 如何定义一个过程式宏( procedural macro )来自定义 `derive` traits
因为在 Rust 里宏仍在开发中,该附录会介绍宏的细节。宏已经改变了,在不久的将来,和语言的其他部分以及从 Rust 1.0 至今的标准库相比,将会以更快的速率改变,因此,和本书其他部分相比,本部分更有可能变得过时。由于 Rust 的稳定性保证,该处的代码将会在后续版本中继续可运行,但可能会以更优的性能或更容易的方式来写那些在此次发布版中无法实现的宏。当你尝试实现该附录任何功能时,请记住这一点。
### 宏和函数的区别
从根本上来说,宏是一种为写其他代码而写代码的方式,即所谓的*元编程*。在附录C中探讨了 `derive` 属性,其生成各种 trait 的实现。我们也在本书中使用过 `println!` 宏和 `vec!` 宏。所有的这些宏 *扩展开* 来以生成比你手写更多的代码。
元编程对于减少大量编写和维护的代码是非常有用的,它也扮演了函数的角色。但宏有一些函数所没有的附加能力。
一个函数标签必须声明函数参数个数和类型。而宏只接受一个可变参数:用一个参数调用 `println!("hello")` 或用两个参数调用 `println!("hello {}", name)` 。而且,宏可以在编译器翻译代码前展开,例如,宏可以在一个给定类型上实现 trait 。因为函数是在运行时被调用,同时 trait 需要在运行时实现,所以函数无法像宏这样。
实现一个宏而不是函数的消极面是宏定义要比函数定义更复杂,因为你正在为写 Rust 代码而写代码。由于这样的间接性,宏定义通常要比函数定义更难阅读、理解以及维护。
宏和函数的另一个区别是:宏定义无法像函数定义那样出现在模块命名空间中。当使用外部包( external crate )时,为了防止无法预料的名字冲突,在导入外部包的同时也必须明确地用 `#[macro_use]` 注解将宏导入到项目中。下面的例子将所有定义在 `serde` 包中的宏导入到当前包中:
```rust,ignore
#[macro_use]
extern crate serde;
```
如果 `extern crate` 能够将宏默认导入而无需使用明确的注解,则会阻止你使用同时定义相同宏名的两个包。在练习中,这样的冲突并不经常遇到,但使用的包越多,越有可能遇到。
宏和函数最重要的区别是:在一个文件中,必须在调用宏`之前`定义或导入宏,然而却可以在任意地方定义或调用函数。
### 通用元编程的声明式宏 `macro_rules!`
在 Rust 中,最广泛使用的宏形式是 *declarative macros* 。通常也指 *macros by example* 、*`macro_rules!` macros* 或简单的 *macros* 。在其核心中,声明式宏使你可以写一些和 Rust 的 `match` 表达式类似的代码。正如在第六章讨论的那样,`match` 表达式是控制结构,其接收一个表达式,与表达式的结果进行模式匹配,然后根据模式匹配执行相关代码。宏也将一个值和包含相关代码的模式进行比较;此种情况下,该值是 Rust 源代码传递给宏的常量,而模式则与源代码结构进行比较,同时每个模式的相关代码成为传递给宏的代码<!-- 这部分翻译自己不太满意-->。所有的这些都在编译时发生。
可以使用 `macro_rules!` 来定义宏。让我们通过查看 `vec!` 宏定义来探索如何使用 `macro_rules!` 结构。第八章讲述了如何使用 `vec!` 宏来生成一个给定值的 vector。例如下面的宏用三个整数创建一个 vector `
```rust
let v: Vec<u32> = vec![1, 2, 3];
```
也可以使用 `vec!` 宏来构造两个整数的 vector 或五个字符串切片的 vector 。但却无法使用函数做相同的事情,因为我们无法预先知道参数值的数量和类型。
在示例 D-1 中来看一个 `vec!` 稍微简化的定义。
```rust
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
```
<span class="caption">示例 D-1: `vec!` 宏定义的一个简化版本</span>
> 注意:标准库中实际定义的 `vec!` 包括预分配适当量的内存。这部分为代码优化,为了让示例简化,此处并没有包含在内。
无论何时导入定义了宏的包,`#[macro_export]` 注解说明宏应该是可用的。 如果没有 `#[macro_export]` 注解,即使凭借包使用 `#[macro_use]` 注解,该宏也不会导入进来,
接着使用 `macro_rules!` 进行了宏定义,且所定义的宏并*不带*感叹号。名字后跟大括号表示宏定义体,在该例中是 `vec`
`vec!` 宏的结构和 `match` 表达式的结构类似。此处有一个单边模式 `( $( $x:expr ),* )` ,后跟 `=>` 以及和模式相关的代码块。如果模式匹配,该相关代码块将被执行。假设这只是这个宏中的模式,且只有一个有效匹配,其他任何匹配都是错误的。更复杂的宏会有多个单边模式。<!-- 此处 arm, one arm 并未找到合适的翻译-->
宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 D-1 中模式片段什么意思。对于全部的宏模式语法,请查阅[参考]。
[参考]: https://github.com/rust-lang/book/blob/master/reference/macros.html
首先,一对括号包含了全部模式。接下来是后跟一对括号的美元符号( `$` ),其通过替代代码捕获了符合括号内模式的值。`$()` 内则是 `$x:expr` ,其匹配 Rust 的任意表达式或给定 `$x` 名字的表达式。
`$()` 之后的逗号说明一个逗号分隔符可以有选择的出现代码之后,这段代码与在 `$()` 中所捕获的代码相匹配。紧随逗号之后的 `*` 说明该模式匹配零个或多个 `*` 之前的任何模式。
当以 `vec![1, 2, 3];` 调用宏时,`$x` 模式与三个表达式 `1`、`2` 和 `3` 进行了三次匹配。
现在让我们来看看这个出现在与此单边模式相关的代码块中的模式:在 `$()*` 部分中所生成的 `temp_vec.push()` 为在匹配到模式中的 `$()` 每一部分而生成。`$x` 由每个与之相匹配的表达式所替换。当以 `vec![1, 2, 3];` 调用该宏时,替换该宏调用所生成的代码会是下面这样:
```rust,ignore
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
```
我们已经定义了一个宏,其可以接收任意数量和类型的参数,同时可以生成能够创建包含指定元素的 vector 的代码。
鉴于大多数 Rust 程序员*使用*宏而不是*写*宏,此处不再深入探讨 `macro_rules!` 。请查阅在线文档或其他资源,如 [“The Little Book of Rust Macros”][tlborm] 来更多地了解如何写宏。
[tlborm]: https://danielkeep.github.io/tlborm/book/index.html
### 自定义 `derive` 的过程式宏
第二种形式的宏叫做*过程式宏* *procedural macros* ),因为它们更像函数(一种过程类型)。过程式宏接收 Rust 代码作为输入,在这些代码上进行操作,然后产生另一些代码作为输出,而非像声明式宏那样匹配对应模式然后以另一部分代码替换当前代码。在书写部分附录时,只能定义过程式宏来使你在一个通过 `derive` 注解来指定 trait 名的类型上实现 trait 。
我们会创建一个 `hello_macro` 包,该包定义了一个关联到 `hello_macro` 函数并以 `HelloMacro` 为名的trait。并非让包的用户为其每一个类型实现`HelloMacro` trait我们将会提供一个过程式宏以便用户可以使用 `#[derive(HelloMacro)]` 注解他们的类型来得到 `hello_macro` 函数的默认实现。该函数的默认实现会打印 `Hello, Macro! My name is TypeName!`,其中 `TypeName` 为定义了 trait 的类型名。换言之,我们会创建一个包,让使用该包的程序员能够写类似示例 D-2 中的代码。
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
extern crate hello_macro;
#[macro_use]
extern crate hello_macro_derive;
use hello_macro::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
```
<span class="caption">示例 D-2: 包用户所写的能够使用过程式宏的代码</span>
运行该代码将会打印 `Hello, Macro! My name is Pancakes!` 第一步是像下面这样新建一个库:
```text
$ cargo new hello_macro --lib
```
接下来,会定义 `HelloMacro` trait 以及其关联函数:
<span class="filename">文件名: src/lib.rs</span>
```rust
pub trait HelloMacro {
fn hello_macro();
}
```
现在有了一个包含函数的 trait 。此时,包用户可以实现该 trait 以达到其期望的功能,像这样:
```rust,ignore
extern crate hello_macro;
use hello_macro::HelloMacro;
struct Pancakes;
impl HelloMacro for Pancakes {
fn hello_macro() {
println!("Hello, Macro! My name is Pancakes!");
}
}
fn main() {
Pancakes::hello_macro();
}
```
然而,他们需要为每一个他们想使用 `hello_macro` 的类型编写实现的代码块。我们希望为其节约这些工作。
另外,我们也无法为 `hello_macro` 函数提供一个能够打印实现了该 trait 的类型的名字的默认实现Rust 没有反射的能力,因此其无法在运行时获取类型名。我们需要一个在运行时生成代码的宏。
下一步是定义过程式宏。在编写该附录时,过程式宏必须在包内。该限制后面可能被取消。构造包和包中宏的惯例如下:对于一个 `foo` 的包来说,一个自定义的派生过程式宏的包被称为 `foo_derive` 。在 `hello_macro` 项目中新建名为 `hello_macro_derive` 的包。
```text
$ cargo new hello_macro_derive --lib
```
由于两个包紧密相关,因此在 `hello_macro` 包的目录下创建过程式宏的包。如果改变在 `hello_macro` 中定义的 trait ,同时也必须改变在 `hello_macro_derive` 中实现的过程式宏。这两个包需要分别发布,编程人员如果使用这些包,则需要同时添加这两个依赖并导入到代码中。我们也可以只用 `hello_macro` 包而将 `hello_macro_derive` 作为一个依赖,并重新导出过程式宏的代码。但我们组织项目的方式使编程人员使用 `hello_macro` 成为可能,即使他们无需 `derive` 的功能。
需要将 `hello_macro_derive` 声明为一个过程式宏的包。同时也需要 `syn``quote` 包中的功能,正如注释中所说,需要将其加到依赖中。为 `hello_macro_derive` 将下面的代码加入到 *Cargo.toml* 文件中。
<span class="filename">文件名: hello_macro_derive/Cargo.toml</span>
```toml
[lib]
proc-macro = true
[dependencies]
syn = "0.11.11"
quote = "0.3.15"
```
为定义一个过程式宏,请将示例 D-3 中的代码放在 `hello_macro_derive` 包的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
```rust,ignore
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
#[proc_macro_derive(HelloMacro)]
pub fn hello_macro_derive(input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let s = input.to_string();
// Parse the string representation
let ast = syn::parse_derive_input(&s).unwrap();
// Build the impl
let gen = impl_hello_macro(&ast);
// Return the generated impl
gen.parse().unwrap()
}
```
<span class="caption">示例 D-3: 大多数过程式宏处理 Rust 代码的代码</span>
注意在 D-3 中分离函数的方式,这将和你几乎所见到或创建的每一个过程式宏都一样,因为这让编写一个过程式宏更加方便。在 `impl_hello_macro` 被调用的地方所选择做的什么依赖于该过程式宏的目的而有所不同。
现在,我们已经介绍了三个包:`proc_macro` 、 [`syn`] 和 [`quote`] 。Rust 自带 `proc_macro` ,因此无需将其加到 *Cargo.toml* 文件的依赖中。`proc_macro` 可以将 Rust 代码转换为相应的字符串。`syn` 则将字符串中的 Rust 代码解析成为一个可以操作的数据结构。`quote` 则将 `syn` 解析的数据结构反过来传入到 Rust 代码中。这些包让解析我们所要处理的有序 Rust 代码变得更简单:为 Rust 编写整个的解析器并不是一件简单的工作。
[`syn`]: https://crates.io/crates/syn
[`quote`]: https://crates.io/crates/quote
当用户在一个类型上指定 `#[derive(HelloMacro)]` 时,`hello_macro_derive` 函数将会被调用。
原因在于我们已经使用 `proc_macro_derive` 及其指定名称对 `hello_macro_derive` 函数进行了注解:`HelloMacro` ,其匹配到 trait 名,这是大多数过程式宏的方便之处。
该函数首先将来自 `TokenStream``输入` 转换为一个名为 `to_string``String` 类型。该 `String` 代表 派生 `HelloMacro` Rust 代码的字符串。在示例 D-2 的例子中,`s` 是 `String` 类型的 `struct Pancakes;` 值,这是因为我们加上了 `#[derive(HelloMacro)]` 注解。
> 注意:编写本附录时,只可以将 `TokenStream` 转换为字符串将来会提供更丰富的API。
现在需要将 `String` 类型的 Rust 代码 解析为一个数据结构中,随后便可以与之交互并操作该数据结构。这正是 `syn` 所做的。`syn` 中的 `parse_derive_input` 函数以一个 `String` 作为参数并返回一个 表示解析出 Rust 代码的 `DeriveInput` 结构体。 下面的代码 展示了从字符串 `struct Pancakes;` 中解析出来的 `DeriveInput` 结构体的相关部分。
```rust,ignore
DeriveInput {
// --snip--
ident: Ident(
"Pancakes"
),
body: Struct(
Unit
)
}
```
该结构体的字段展示了我们解析的 Rust 代码是一个元组结构体,其 `ident` identifier表示名字`Pancakes` 。该结构体里面有更多字段描述了所有有序 Rust 代码,查阅 [`syn`
documentation for `DeriveInput`][syn-docs] 以获取更多信息。
[syn-docs]: https://docs.rs/syn/0.11.11/syn/struct.DeriveInput.html
此时,尚未定义 `impl_hello_macro` 函数,其用于构建所要包含在内的 Rust 新代码。但在定义之前,要注意 `hello_macro_derive` 函数的最后一部分使用了 `quote` 包中的 `parse` 函数,该函数将 `impl_hello_macro` 的输出返回给 `TokenStream` 。所返回的 `TokenStream` 会被加到我们的包用户所写的代码中,因此,当用户编译他们的包时,他们会获取到我们所提供的额外功能。
你也注意到,当调用 `parse_derive_input``parse` 失败时,我们调用 `unwrap` 来抛出异常。在过程式宏中,有必要错误上抛异常,因为 `proc_macro_derive` 函数必须返回 `TokenStream` 而不是 `Result` ,以此来符合过程式宏的 API 。我们已经选择用 `unwrap` 来简化了这个例子;在生产中的代码里,你应该通过 `panic!``expect` 来提供关于发生何种错误的更加明确的错误信息。
现在我们有了将注解的 Rust 代码从 `TokenStream` 转换为 `String``DeriveInput` 实例的代码,让我们来创建在注解类型上实现 `HelloMacro` trait 的代码。
<span class="filename">文件名: hello_macro_derive/src/lib.rs</span>
```rust,ignore
fn impl_hello_macro(ast: &syn::DeriveInput) -> quote::Tokens {
let name = &ast.ident;
quote! {
impl HelloMacro for #name {
fn hello_macro() {
println!("Hello, Macro! My name is {}", stringify!(#name));
}
}
}
}
```
我们得到一个包含以 `ast.ident` 作为注解类型名字(标识符)的 `Ident` 结构体实例。示例 D-2 中的代码说明 `name` 会是 `Ident("Pancakes")`
`quote!` 宏让我们编写我们想要返回的代码,并可以将其传入进 `quote::Tokens` 。这个宏也提供了一些非常酷的模板机制;我们可以写 `#name` ,然后 `quote!` 会以 名为 `name` 的变量值来替换它。你甚至可以做些与这个正则宏任务类似的重复事情。查阅 [the `quote` crates
docs][quote-docs] 来获取详尽的介绍。
[quote-docs]: https://docs.rs/quote
我们期望我们的过程式宏能够为通过 `#name` 获取到的用户注解类型生成 `HelloMacro` trait 的实现。该 trait 的实现有一个函数 `hello_macro` ,其函数体包括了我们期望提供的功能:打印 `Hello, Macro! My name is` 和注解的类型名。
此处所使用的 `stringify!` 为 Rust 内置宏。其接收一个 Rust 表达式,如 `1 + 2` 然后在编译时将表达式转换为一个字符串常量,如 `"1 + 2"` 。这与 `format!``println!` 是不同的,它计算表达式并将结果转换为 `String` 。有一种可能的情况是,所输入的 `#name` 可能是一个需要打印的表达式,因此我们用 `stringify!``stringify!` 编译时也保留了一份将 `#name` 转换为字符串之后的内存分配。
此时,`cargo build` 应该都能成功编译 `hello_macro``hello_macro_derive` 。我们将这些 crate 连接到示例 D-2 的代码中来看看过程式宏的行为。在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro``hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro``hello_macro_derive` 的版本发布到 [*https://crates.io/*][crates.io] 上,其应为正规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖:
[crates.io]: https://crates.io/
```toml
[dependencies]
hello_macro = { path = "../hello_macro" }
hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
```
把示例 D-2 中的代码放在 *src/main.rs* ,然后执行 `cargo run` 其应该打印 `Hello, Macro! My name is Pancakes!` 。从过程式宏中实现的 `HelloMacro` trait 被包括在内,但并不包含 `pancakes` 的包,需要实现它。`#[derive(HelloMacro)]` 添加了该 trait 的实现。<!-- 中间句子翻译不是太好 -->
### 宏的前景
在将来Rust 仍会扩展声明式宏和过程式宏。Rust会通过 `macro` 使用一个更好的声明式宏系统,以及为较之 `derive` 的更强大的任务增加更多的过程式宏类型。在本书出版时,这些系统仍然在开发中,请查阅 Rust 在线文档以获取最新信息。

@ -1 +1,25 @@
# E - 本书翻译 ## 附录E: 本书翻译
> [appendix-05-translation.md](https://github.com/rust-lang/book/blob/master/second-edition/src/appendix-05-translation.md)
> <br />
> commit 9c3756a33e6c1aa07a762ffa853fcdfcffb48ddc
一些非英语语言的资源。多数仍在翻译中;查阅[翻译标签][label]来帮助我们或使我们知道新的翻译!
[label]: https://github.com/rust-lang/book/issues?q=is%3Aopen+is%3Aissue+label%3ATranslations
- [Português](https://github.com/rust-br/rust-book-pt-br) (BR)
- [Português](https://github.com/nunojesus/rust-book-pt-pt) (PT)
- [Tiếng việt](https://github.com/hngnaig/rust-lang-book/tree/vi-VN)
- [简体中文](https://github.com/KaiserY/trpl-zh-cn)
- [Українська](https://github.com/pavloslav/rust-book-uk-ua)
- [Español](https://github.com/thecodix/book)
- [Italiano](https://github.com/AgeOfWar/rust-book-it)
- [Русский](https://github.com/iDeBugger/rust-book-ru)
- [한국어](https://github.com/rinthel/rust-lang-book-ko)
- [日本語](https://github.com/hazama-yuinyan/book)
- [Français](https://github.com/quadrifoglio/rust-book-fr)
- [Polski](https://github.com/paytchoo/book-pl)
- [עברית](https://github.com/idanmel/rust-book-heb)
- [Cebuano](https://github.com/agentzero1/book)
- [Tagalog](https://github.com/josephace135/book)

@ -1 +1 @@
# F - 最新功能 ## F - 最新功能

@ -1 +1 @@
# G - Rust 是如何开发的与 “Nightly Rust” ## G - Rust 是如何开发的与 “Nightly Rust”

@ -50,7 +50,7 @@ Rust 适用于渴望编程语言的速度与稳定性的开发者。对于速度
总体来说,本书假设你会从头到尾顺序阅读。稍后的章节建立在之前章节概念的基础上,同时之前的章节可能不会深入挖掘主题的细节;通常稍后的章节会重新提到这些主题。 总体来说,本书假设你会从头到尾顺序阅读。稍后的章节建立在之前章节概念的基础上,同时之前的章节可能不会深入挖掘主题的细节;通常稍后的章节会重新提到这些主题。
你会在本书中发现两类章节:概念章节和项目章节。在项目章节中,我们学习 Rust 的某个方面。在项目章节中,我们应用目前所学的知识一同构建小的程序。第二、十二和二十章是项目章节;其余则是概念章节。 你会在本书中发现两类章节:概念章节和项目章节。在概念章节中,我们学习 Rust 的某个方面。在项目章节中,我们应用目前所学的知识一同构建小的程序。第二、十二和二十章是项目章节;其余则是概念章节。
另外,第二章是一个 Rust 语言的介绍实践。我们会在高层次介绍一些概念,并在稍后的章节提供额外的细节。如果你希望立刻就动手实践一下,第二章正好适合你。开始阅读时,你甚至可能希望略过第三章,它介绍了 Rust 中类似其他编程语言中的功能,并直接阅读第四章学习 Rust 的所有权系统。然而,如果你是特别重视细节的学习者,并倾向于在继续之前学习每一个细节,你可能希望略过第二章并直接阅读第三章,并在想要构建项目来实践这些细节时再回到第二章。 另外,第二章是一个 Rust 语言的介绍实践。我们会在高层次介绍一些概念,并在稍后的章节提供额外的细节。如果你希望立刻就动手实践一下,第二章正好适合你。开始阅读时,你甚至可能希望略过第三章,它介绍了 Rust 中类似其他编程语言中的功能,并直接阅读第四章学习 Rust 的所有权系统。然而,如果你是特别重视细节的学习者,并倾向于在继续之前学习每一个细节,你可能希望略过第二章并直接阅读第三章,并在想要构建项目来实践这些细节时再回到第二章。

@ -4,7 +4,7 @@
> <br> > <br>
> commit d1448cef370442b51e69298fb734fe29a3d14577 > commit d1448cef370442b51e69298fb734fe29a3d14577
现在安装好了 Rust让我们来编写第一个 Rust 程序。当学习一门新语言的时候,使用该语言在屏幕上打印 “Hello, world!” 是一项传统,这里我们将遵循这个传统! 既然安装好了 Rust让我们来编写第一个 Rust 程序。当学习一门新语言的时候,使用该语言在屏幕上打印 “Hello, world!” 是一项传统,这里我们将遵循这个传统!
> 注意本书假设你熟悉基本的命令行操作。Rust 对于你的编辑器、工具以及代码位于何处并没有特定的要求如果相比命令行你更倾向于使用集成开发环境IDE请随意使用合意的 IDE。目前很多 IDE 拥有不同程度的 Rust 支持;查看 IDE 文档了解更多细节。目前 Rust 团队已经致力于提供强大的 IDE 支持,而且进展飞速! > 注意本书假设你熟悉基本的命令行操作。Rust 对于你的编辑器、工具以及代码位于何处并没有特定的要求如果相比命令行你更倾向于使用集成开发环境IDE请随意使用合意的 IDE。目前很多 IDE 拥有不同程度的 Rust 支持;查看 IDE 文档了解更多细节。目前 Rust 团队已经致力于提供强大的 IDE 支持,而且进展飞速!
@ -143,6 +143,6 @@ $ ./main # or .\main.exe on Windows
如果 *main.rs* 是上文所述的 “Hello, world!” 程序,它将会在终端上打印 `Hello, world!` 如果 *main.rs* 是上文所述的 “Hello, world!” 程序,它将会在终端上打印 `Hello, world!`
来自 Ruby、Python 或 JavaScript 这样的动态类型语言背景的同学可能不太习惯将编译和执行分为两个单独的步骤。Rust 是一种 **预编译静态类型***ahead-of-time compiled*)语言,这意味着你可以编译程序并将其交与他人,们不需要安装 Rust 即可运行。如果你给他人一个 `.rb`、`.py` 或 `.js` 文件,他们需要先分别安装 RubyPythonJavaScript 实现运行时环境VM。不过在这些语言中只需要一句命令就可以编译和执行程序。这一切都是语言设计上的权衡取舍。 来自 Ruby、Python 或 JavaScript 这样的动态类型语言背景的同学可能不太习惯将编译和执行分为两个单独的步骤。Rust 是一种 **预编译静态类型***ahead-of-time compiled*)语言,这意味着你可以编译程序并将其交与他人,们不需要安装 Rust 即可运行。如果你给他人一个 `.rb`、`.py` 或 `.js` 文件,他们需要先分别安装 RubyPythonJavaScript 实现运行时环境VM。不过在这些语言中只需要一句命令就可以编译和执行程序。这一切都是语言设计上的权衡取舍。
仅仅使用 `rustc` 编译简单程序是没问题的,不过随着项目的增长,你可能需要控制你项目的方方面面,并且更容易地将代码分享给其它人或项目。接下来,我们要介绍一个叫做 Cargo 的工具,它会帮助你编写真实世界中的 Rust 程序。 仅仅使用 `rustc` 编译简单程序是没问题的,不过随着项目的增长,你可能需要控制你项目的方方面面,并且更容易地将代码分享给其它人或项目。接下来,我们要介绍一个叫做 Cargo 的工具,它会帮助你编写真实世界中的 Rust 程序。

@ -292,7 +292,7 @@ $ cargo build
[cratesio]: https://crates.io [cratesio]: https://crates.io
在更新完 registry 后Cargo 检查 `[dependencies]` 段落并下载缺失的 create 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 的拷贝,因为 `rand` 依赖 `libc` 来正常工作。下载完成后Rust 编译依赖,然后使用这些依赖编译项目。 在更新完 registry 后Cargo 检查 `[dependencies]` 段落并下载缺失的 crate 。本例中,虽然只声明了 `rand` 一个依赖,然而 Cargo 还是额外获取了 `libc` 的拷贝,因为 `rand` 依赖 `libc` 来正常工作。下载完成后Rust 编译依赖,然后使用这些依赖编译项目。
如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 完成提示之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。 如果不做任何修改,立刻再次运行 `cargo build`,则不会看到任何除了 `Finished` 完成提示之外的输出。Cargo 知道它已经下载并编译了依赖,同时 *Cargo.toml* 文件也没有变动。Cargo 还知道代码也没有任何修改,所以它不会重新编译代码。因为无事可做,它简单的退出了。

@ -323,9 +323,9 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
变量的所有权总是遵循相同的模式:将值赋值给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。 变量的所有权总是遵循相同的模式:将值赋值给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 `drop` 被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取并接着返回所有权可能有些冗余。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,另外我们也可能想要返回函数体产生的任何(不止一个)数据 在每一个函数中都获取并接着返回所有权可能有些冗余。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传递出去再传回来就有点烦人了,如果真的这样做,有时一个函数就需要有多个返回值
可以使用元组来返回多个值,像这样: 当然,我们可以使用元组来返回多个值,像这样:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>

@ -2,9 +2,9 @@
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-01-defining-structs.md) > [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-01-defining-structs.md)
> <br> > <br>
> commit e143d8fca3f914811b1388755ff4d325e9d20cc2 > commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
我们在第三章讨论过,结构体与元组类似。就像元组,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。 结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字使得结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,它们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体: 定义结构体,需要使用 `struct` 关键字并为整个结构体提供一个名字。结构体的名字需要描述它所组合的数据的意义。接着,在大括号中,定义每一部分数据的名字,它们被称作 **字段***field*),并定义字段类型。例如,示例 5-1 展示了一个储存用户账号信息的结构体:
@ -19,7 +19,7 @@ struct User {
<span class="caption">示例 5-1`User` 结构体定义</span> <span class="caption">示例 5-1`User` 结构体定义</span>
一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户: 一旦定义了结构体后,为了使用它,通过为每个字段指定具体值来创建这个结构体的 **实例**。创建一个实例需要以结构体的名字开头,接着在大括号中使用 `key: value` 键-值对的形式提供字段,其中 key 是字段的名字value 是需要储存在字段中的数据值。实例中具体说明字段的顺序不需要和它们在结构体中声明的顺序一致。换句话说,结构体的定义就像一个类型的通用模板,而实例则会在这个模板中放入特定数据来创建这个类型的值。例如,可以像示例 5-2 这样来声明一个特定的用户:
```rust ```rust
# struct User { # struct User {
@ -59,9 +59,9 @@ let mut user1 = User {
user1.email = String::from("anotheremail@example.com"); user1.email = String::from("anotheremail@example.com");
``` ```
<span class="caption">示例 5-3改变 `User` 结构体 `email` 字段的值</span> <span class="caption">示例 5-3改变 `User` 实例 `email` 字段的值</span>
注意整个实例必须是可变的Rust 并不允许只将特定字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式构造一个结构体,从函数隐式的返回一个结构体的新实例。 注意整个实例必须是可变的Rust 并不允许只将特定字段标记为可变。另外需要注意同其他任何表达式一样,我们可以在函数体的最后一个表达式中构造一个结构体的新实例,来隐式地返回这个实例。
示例 5-4 显示了一个返回带有给定的 `email``username``User` 结构体的实例的 `build_user` 函数。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1` 示例 5-4 显示了一个返回带有给定的 `email``username``User` 结构体的实例的 `build_user` 函数。`active` 字段的值为 `true`,并且 `sign_in_count` 的值为 `1`
@ -91,7 +91,6 @@ fn build_user(email: String, username: String) -> User {
因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法***field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email``username` 了,如示例 5-5 所示。 因为示例 5-4 中的参数名与字段名都完全相同,我们可以使用 **字段初始化简写语法***field init shorthand*)来重写 `build_user`,这样其行为与之前完全相同,不过无需重复 `email``username` 了,如示例 5-5 所示。
如果有变量与字段同名的话,你可以使用 **字段初始化简写语法***field init shorthand*)。这可以让创建新的结构体实例的函数更为简练。
```rust ```rust
# struct User { # struct User {
@ -115,11 +114,11 @@ fn build_user(email: String, username: String) -> User {
这里我们创建了一个新的 `User` 结构体实例,它有一个叫做 `email` 的字段。我们想要将 `email` 字段的值设置为 `build_user` 函数 `email` 参数的值。因为 `email` 字段与 `email` 参数有着相同的名称,则只需编写 `email` 而不是 `email: email` 这里我们创建了一个新的 `User` 结构体实例,它有一个叫做 `email` 的字段。我们想要将 `email` 字段的值设置为 `build_user` 函数 `email` 参数的值。因为 `email` 字段与 `email` 参数有着相同的名称,则只需编写 `email` 而不是 `email: email`
### 使用结构体更新语法从其他对象创建对象 ### 使用结构体更新语法从其他实例创建实例
可以从老的对象创建新的对象常常是很有帮助的,即复用大部分老对象的值并只改变一部分值。这可以通过 **结构体更新语法***struct update syntax*)实现。 使用旧实例的大部分值但改变其部分值来创建一个新的结构体实例通常是很有帮助的。这可以通过 **结构体更新语法***struct update syntax*)实现。
作为开始,示例 5-6 展示了如何不使用更新语法来在 `user2` 中创建一个新 `User` 实例。我们为 `email``username` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值: 首先,示例 5-6 展示了如何不使用更新语法来在 `user2` 中创建一个新 `User` 实例。我们为 `email``username` 设置了新的值,其他值则使用了实例 5-2 中创建的 `user1` 中的同名值:
```rust ```rust
# struct User { # struct User {
@ -172,7 +171,7 @@ let user2 = User {
<span class="caption">示例 5-7使用结构体更新语法为一个 `User` 实例设置新的 `email``username` 值,不过其余值来自 `user1` 变量中实例的字段</span> <span class="caption">示例 5-7使用结构体更新语法为一个 `User` 实例设置新的 `email``username` 值,不过其余值来自 `user1` 变量中实例的字段</span>
例 5-7 中的代码也在 `user2` 中创建了一个新实例,其有不同的 `email``username` 值不过 `active``sign_in_count` 字段的值与 `user1` 相同。 例 5-7 中的代码也在 `user2` 中创建了一个新实例,其有不同的 `email``username` 值不过 `active``sign_in_count` 字段的值与 `user1` 相同。
### 使用没有命名字段的元组结构体来创建不同的类型 ### 使用没有命名字段的元组结构体来创建不同的类型
@ -198,7 +197,7 @@ let origin = Point(0, 0, 0);
> >
> 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。 > 在示例 5-1 中的 `User` 结构体的定义中,我们使用了自身拥有所有权的 `String` 类型而不是 `&str` 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。
> >
> 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期,比如这样: > 可以使结构体储存被其他对象拥有的数据的引用,不过这么做的话需要用上 **生命周期***lifetimes*),这是一个第十章会讨论的 Rust 功能。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中储存一个引用而不指定生命周期将是无效的,比如这样:
> >
> <span class="filename">文件名: src/main.rs</span> > <span class="filename">文件名: src/main.rs</span>
> >

@ -2,11 +2,11 @@
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-02-example-structs.md) > [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-02-example-structs.md)
> <br> > <br>
> commit 7bf137c1b8f176638c0a7fa136d2e6bdc1f6e7d3 > commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。 为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。
使用 Cargo 来创建一个叫做 *rectangles* 的新二进制程序,它会获取一个长方形以像素为单位的宽度和高度并计算它的面积。示例 5-8 中是项目的 *src/main.rs* 文件为此实现的一个小程序: 使用 Cargo 来创建一个叫做 *rectangles* 的新二进制程序,它会获取一个长方形以像素为单位的宽度和高度并计算它的面积。示例 5-8 中是项目的 *src/main.rs* 文件为此实现的一个小程序:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -34,8 +34,6 @@ fn area(width: u32, height: u32) -> u32 {
The area of the rectangle is 1500 square pixels. The area of the rectangle is 1500 square pixels.
``` ```
### 使用元组重构
虽然示例 5-8 可以运行,并调用 `area` 函数用长方形的每个维度来计算出面积,不过我们可以做的更好。宽度和高度是相关联的,因为他们在一起才能定义一个长方形。 虽然示例 5-8 可以运行,并调用 `area` 函数用长方形的每个维度来计算出面积,不过我们可以做的更好。宽度和高度是相关联的,因为他们在一起才能定义一个长方形。
这些代码的问题突显在 `area` 的签名上: 这些代码的问题突显在 `area` 的签名上:
@ -44,7 +42,11 @@ The area of the rectangle is 1500 square pixels.
fn area(width: u32, height: u32) -> u32 { fn area(width: u32, height: u32) -> u32 {
``` ```
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “将值组合进元组” 部分已经讨论过了一种可行的方法:元组。示例 5-9 是另一个使用元组的版本: 函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却哪里也没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “元组类型” 部分已经讨论过了一种可行的方法:元组。
### 使用元组重构
示例 5-9 展示了使用元组的另一个程序版本。
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -63,11 +65,11 @@ fn area(dimensions: (u32, u32)) -> u32 {
} }
``` ```
<span class="caption">示例 5-8:使用元组来指定长方形的宽高</span> <span class="caption">示例 5-9:使用元组来指定长方形的宽高</span>
在某种程度上说这个程序更好一点了。元组帮助我们增加了一些结构性,现在在调用 `area` 的时候只需传递一个参数。不过在另一方面这个方法却更不明确了:元组并没有给出它元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分: 在某种程度上说这个程序更好一点了。元组帮助我们增加了一些结构性,并且现在只需传一个参数。不过在另一方面这个版本却有一点不明确了:元组并没有给出元素的名称,所以计算变得更费解了,因为不得不使用索引来获取元组的每一部分:
在面积计算时混淆宽高并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引 `0``width``1``height`。如果其他人要使用这些代码,他们也不得不搞清楚并记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。 在面积计算时混淆宽高并没有什么问题,不过当在屏幕上绘制长方形时就有问题了!我们将不得不记住元组索引 `0``width``1``height`。如果其他人要使用这些代码,他们也不得不搞清楚并记住他们。容易忘记或者混淆这些值而造成错误,因为我们没有表明代码中数据的意义。
### 使用结构体重构:赋予更多意义 ### 使用结构体重构:赋予更多意义
@ -99,13 +101,13 @@ fn area(rectangle: &Rectangle) -> u32 {
这里我们定义了一个结构体并称其为 `Rectangle`。在 `{}` 中定义了字段 `width``height`,都是 `u32` 类型的。接着在 `main` 中,我们创建了一个宽度为 30 和高度为 50 的 `Rectangle` 的具体实例。 这里我们定义了一个结构体并称其为 `Rectangle`。在 `{}` 中定义了字段 `width``height`,都是 `u32` 类型的。接着在 `main` 中,我们创建了一个宽度为 30 和高度为 50 的 `Rectangle` 的具体实例。
函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&` 函数 `area` 现在被定义为接收一个名叫 `rectangle` 的参数,其类型是一个结构体 `Rectangle` 实例的不可变借用。第四章讲到过,我们希望借用结构体而不是获取它的所有权这样 `main` 函数就可以保持 `rect1` 的所有权并继续使用它,所以这就是为什么在函数签名和调用的地方会有 `&`
`area` 函数访问 `Rectangle``width``height` 字段。`area` 的签名现在明确的表明了我们的意图:通过其 `width``height` 字段,计算一个 `Rectangle` 的面积。这表明了宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0``1` 。结构体胜在更清晰明了。 `area` 函数访问 `Rectangle``width``height` 字段。`area` 的签名现在明确的表明了我们的意图:通过其 `width``height` 字段,计算一个 `Rectangle` 的面积。这表明了宽高是相互联系的,并为这些值提供了描述性的名称而不是使用元组的索引值 `0``1` 。结构体胜在更清晰明了。
### 通过派生 trait 增加实用功能 ### 通过派生 trait 增加实用功能
如果能够在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值就更好了。示例 5-11 像第二章、第三章和第四章那样尝试了 `println!` 宏: 如果能够在调试程序时打印出 `Rectangle` 实例来查看其所有字段的值就更好了。示例 5-11 像前面章节那样尝试使用 `println!` 宏。但这并不行。
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -141,7 +143,7 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait它允许我们在调试代码时以一种对开发者有帮助的方式打印出结构体。 让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait它允许我们在调试代码时以一种对开发者有帮助的方式打印出结构体。
让我们试试运行这个变化。见鬼了!仍然能看到一个错误: 以这个改变运行程序。见鬼了!仍然能看到一个错误:
```text ```text
error[E0277]: the trait bound `Rectangle: std::fmt::Debug` is not satisfied error[E0277]: the trait bound `Rectangle: std::fmt::Debug` is not satisfied

@ -2,7 +2,7 @@
> [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-03-method-syntax.md) > [ch05-03-method-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-03-method-syntax.md)
> <br> > <br>
> commit ec65990849230388e4ce4db5b7a0cb8a0f0d60e2 > commit c560db1e0145d5a64b9415c9cfe463c7dac31ab8
**方法** 与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含一段该方法在某处被调用时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。 **方法** 与函数类似:它们使用 `fn` 关键字和名称声明,可以拥有参数和返回值,同时包含一段该方法在某处被调用时会执行的代码。不过方法与函数是不同的,因为它们在结构体的上下文中被定义(或者是枚举或 trait 对象的上下文,将分别在第六章和第十七章讲解),并且它们第一个参数总是 `self`,它代表调用该方法的结构体实例。
@ -47,11 +47,11 @@ fn main() {
> ### `->`运算符到哪去了? > ### `->`运算符到哪去了?
> >
> 像在 C++ 这样的语言中,有两个不同的运算符来调用方法:`.` 直接在对象上调用方法,而 `->` 在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果 `object` 是一个指针,那么 `object->something()` 就像 `(*object).something()` 一样。 > 像在 C/C++ 这样的语言中,有两个不同的运算符来调用方法:`.` 直接在对象上调用方法,而 `->` 在一个对象的指针上调用方法这时需要先解引用dereference指针。换句话说如果 `object` 是一个指针,那么 `object->something()` 就像 `(*object).something()` 一样。
> >
> Rust 并没有一个与 `->` 等效的运算符相反Rust 有一个叫 **自动引用和解引用***automatic referencing and dereferencing*)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。 > Rust 并没有一个与 `->` 等效的运算符相反Rust 有一个叫 **自动引用和解引用***automatic referencing and dereferencing*)的功能。方法调用是 Rust 中少数几个拥有这种行为的地方。
> >
> 这是它如何工作的:当使用 `object.something()` 调用方法时Rust 会自动增`&`、`&mut` 或 `*` 以便使 `object` 符合方法的签名。也就是说,这些代码是等价的: > 他是这样工作的:当使用 `object.something()` 调用方法时Rust 会自动添`&`、`&mut` 或 `*` 以便使 `object` 符合方法的签名。也就是说,这些代码是等价的:
> >
> ```rust > ```rust
> # #[derive(Debug,Copy,Clone)] > # #[derive(Debug,Copy,Clone)]
@ -78,7 +78,7 @@ fn main() {
### 带有更多参数的方法 ### 带有更多参数的方法
让我们更多的实践一下方法,通过为 `Rectangle` 结构体实现第二个方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `self` 能否完全包含第二个长方形,如果能则返回 `true` ,如果不能则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了: 让我们练习通过实现 `Rectangle` 结构体上的另一方法来使用方法。这回,我们让一个 `Rectangle` 的实例获取另一个 `Rectangle` 实例并返回 `true` 如果 `self` 能完全包含第二个长方形,否则返回 `false`。一旦定义了 `can_hold` 方法,就可以运行示例 5-14 中的代码了:
<span class="filename">文件名: src/main.rs</span> <span class="filename">文件名: src/main.rs</span>
@ -95,7 +95,7 @@ fn main() {
<span class="caption">示例 5-14展示还未实现的 `can_hold` 方法的应用</span> <span class="caption">示例 5-14展示还未实现的 `can_hold` 方法的应用</span>
同时我们希望看到如下输出,因为 `rect2`宽高都小于 `rect1`,而 `rect3``rect1` 要宽: 同时我们希望看到如下输出,因为 `rect2`两个维度都小于 `rect1`,而 `rect3``rect1` 要宽:
```text ```text
Can rect1 hold rect2? true Can rect1 hold rect2? true

@ -1,24 +1,24 @@
## 字符串存储 UTF-8 编码的文本 ## 使用字符串存储 UTF-8 编码的文本
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md) > [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
> <br> > <br>
> commit c2fd7b2d39c4130dd17bb99c101ac94af83d1a44 > commit d0e83220e083ef87880e6a04f030b90c9af9385b
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。 第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面内容的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
字符串出现在集合章节的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。 在集合章节中讨论字符串的原因是,字符串是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引` String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。
### 什么是字符串? ### 什么是字符串?
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:`str`,字符串 slice它通常以被借用的形式出现`&str`。第四章讲到了 **字符串 slice**:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。 在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:`str`,字符串 slice它通常以被借用的形式出现`&str`。第四章讲到了 **字符串 slice**:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。
称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str`类型,而不仅仅是其中之一。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slice 都是 UTF-8 编码的。 称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str` 类型,而不仅仅是其中之一。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slice 都是 UTF-8 编码的。
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。`*String`/`*Str` 的命名类似,它们通常也提供有所有权和可借用的变体,就比如说 `String`/`&str`。这些字符串类型在储存的编码或内存表现形式上可能有所不同。本章将不会讨论其他这些字符串类型;查看 API 文档来更多的了解如何使用它们以及各自适合的场景。 Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。看到这些由 `String` 或是 `Str` 结尾的名字了吗?这对应着它们提供的所有权和可借用的字符串变体,就像是你之前看到的 `String``str`。举例而言,这些字符串类型能够以不同的编码或内存表现形式上以不同的形式存储文本内容。本章将不会讨论其他这些字符串类型,查看 API 文档来更多的了解如何使用它们以及各自适合的场景。
### 新建字符串 ### 新建字符串
很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,如示例 8-11 所示 很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,如示例 8-11 所示
```rust ```rust
let mut s = String::new(); let mut s = String::new();
@ -28,7 +28,7 @@ let mut s = String::new();
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。 这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。
通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值就可以。示例 8-12 展示了两个例子: 通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,字符串字面值也实现了它。示例 8-12 展示了两个例子。
```rust ```rust
let data = "initial contents"; let data = "initial contents";
@ -43,7 +43,7 @@ let s = "initial contents".to_string();
这些代码会创建包含 `initial contents` 的字符串。 这些代码会创建包含 `initial contents` 的字符串。
也可以使用 `String::from` 函数来从字符串字面值创建 `String`。示例 8-13 中的代码代码等同于使用 `to_string` 也可以使用 `String::from` 函数来从字符串字面值创建 `String`。示例 8-13 中的代码代码等同于使用 `to_string`
```rust ```rust
let s = String::from("initial contents"); let s = String::from("initial contents");
@ -53,7 +53,7 @@ let s = String::from("initial contents");
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。 因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示 记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示
```rust ```rust
let hello = String::from("السلام عليكم"); let hello = String::from("السلام عليكم");
@ -79,7 +79,7 @@ let hello = String::from("Hola");
#### 使用 push 附加字符串 #### 使用 push 附加字符串
可以通过 `push_str` 方法来附加字符串 slice从而使 `String` 变长,如示例 8-15 所示 可以通过 `push_str` 方法来附加字符串 slice从而使 `String` 变长,如示例 8-15 所示
```rust ```rust
let mut s = String::from("foo"); let mut s = String::from("foo");
@ -88,12 +88,12 @@ s.push_str("bar");
<span class="caption">示例 8-15使用 `push_str` 方法向 `String` 附加字符串 slice</span> <span class="caption">示例 8-15使用 `push_str` 方法向 `String` 附加字符串 slice</span>
执行这两行代码之后 `s` 将会包含 `foobar`。`push_str` 方法获取字符串 slice因为我们并不需要获取参数的所有权。例如示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 中后自身不能被使用就糟糕了 执行这两行代码之后 `s` 将会包含 `foobar`。`push_str` 方法获取字符串 slice因为我们并不需要获取参数的所有权。例如示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 中后自身不能被使用就糟糕了
```rust ```rust
let mut s1 = String::from("foo"); let mut s1 = String::from("foo");
let s2 = "bar"; let s2 = "bar";
s1.push_str(&s2); s1.push_str(s2);
println!("s2 is {}", s2); println!("s2 is {}", s2);
``` ```
@ -101,7 +101,7 @@ println!("s2 is {}", s2);
如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作! 如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作!
`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 l 加入 `String` 的代码 `push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 l 加入 `String` 的代码
```rust ```rust
let mut s = String::from("lo"); let mut s = String::from("lo");
@ -114,12 +114,12 @@ s.push('l');
#### 使用 + 运算符或 `format!` 宏连接字符串 #### 使用 + 运算符或 `format!` 宏连接字符串
通常我们希望将两个已知的字符串合并在一起。一种办法是像这样使用 `+` 运算符,如示例 8-18 所示 通常你会希望将两个已知的字符串合并在一起。一种办法是像这样使用 `+` 运算符,如示例 8-18 所示
```rust ```rust
let s1 = String::from("Hello, "); let s1 = String::from("Hello, ");
let s2 = String::from("world!"); let s2 = String::from("world!");
let s3 = s1 + &s2; // Note that s1 has been moved here and can no longer be used let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
``` ```
<span class="caption">示例 8-18使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span> <span class="caption">示例 8-18使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span>
@ -162,7 +162,7 @@ let s = format!("{}-{}-{}", s1, s2, s3);
### 索引字符串 ### 索引字符串
在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果我们尝试使用索引语法访问 `String` 的一部分,会出现一个错误。考虑一下如示例 8-19 中所示的无效代码 在很多语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果尝试使用索引语法访问 `String` 的一部分,会出现一个错误。考虑一下如示例 8-19 中所示的无效代码
```rust,ignore ```rust,ignore
let s1 = String::from("hello"); let s1 = String::from("hello");
@ -193,7 +193,7 @@ error[E0277]: the trait bound `std::string::String: std::ops::Index<{integer}>`
let len = String::from("Hola").len(); let len = String::from("Hola").len();
``` ```
在这里,`len` 的值是,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢? 在这里,`len` 的值是 4 ,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是阿拉伯数字 3
```rust ```rust
let len = String::from("Здравствуйте").len(); let len = String::from("Здравствуйте").len();
@ -206,13 +206,13 @@ let hello = "Здравствуйте";
let answer = &hello[0]; let answer = &hello[0];
``` ```
`answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。返回字节值可能不是人们希望看到的,即便是只有拉丁字母时:`&"hello"[0]` 会返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。 `answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母: 即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。
#### 字节、标量值和字形簇!天呐! #### 字节、标量值和字形簇!天呐!
这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。 这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 `Vec` 中的 `u8` 值看起来像这样: 比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 `u8` 值看起来像这样:
```text ```text
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, [224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
@ -237,7 +237,7 @@ Rust 提供了多种不同的方式来解释计算机储存的原始字符串数
### 字符串 slice ### 字符串 slice
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表你需要一个字符串 slice相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice 索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表你需要一个字符串 slice相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice
```rust ```rust
let hello = "Здравствуйте"; let hello = "Здравствуйте";
@ -253,13 +253,13 @@ let s = &hello[0..4];
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4 thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4
``` ```
你应该小心谨慎的使用这个操作,因为可能会使你的程序崩溃。 你应该小心谨慎的使用这个操作,因为这么做可能会使你的程序崩溃。
### 遍历字符串的方法 ### 遍历字符串的方法
幸运的是,这里还有其他获取字符串元素的方式。 幸运的是,这里还有其他获取字符串元素的方式。
如果我们需要操作单独的 Unicode 标量值,最好的选择是使用 `chars` 方法。对 “नमस्ते” 调用 `chars` 方法会将其分开并返回六个 `char` 类型的值,接着就可以遍历其结果来访问每一个元素了: 如果需要操作单独的 Unicode 标量值,最好的选择是使用 `chars` 方法。对 “नमस्ते” 调用 `chars` 方法会将其分开并返回六个 `char` 类型的值,接着就可以遍历其结果来访问每一个元素了:
```rust ```rust
for c in "नमस्ते".chars() { for c in "नमस्ते".chars() {
@ -286,14 +286,16 @@ for b in "नमस्ते".bytes() {
} }
``` ```
这些代码会打印出组成 `String` 的 18 个字节,开头是这样的 这些代码会打印出组成 `String` 的 18 个字节:
```text ```text
224 224
164 164
168 168
224 224
// ... etc // --snip--
165
135
``` ```
不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。 不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。

@ -53,7 +53,7 @@ fn main() {
<span class="caption">示例 12-4读取第二个参数所指定的文件内容</span> <span class="caption">示例 12-4读取第二个参数所指定的文件内容</span>
首先,我们增加了更多的 `use` 语句来引入标准库中的相关部分:需要 `std::fs::File` 来处理文件,而 `std::io::prelude::*` 则包含许多对于 I/O 包括文件 I/O 有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,`std::io` 也有其自己的 prelude 来引入处理 I/O 时所需的通用内容。不同于默认的 prelude必须显式 `use` 位于 `std::io` 中的 prelude。 首先,我们增加了更多的 `use` 语句来引入标准库中的相关部分:需要 `std::fs::File` 来处理文件,而 `std::io::prelude::*` 则包含许多对于 I/O(包括文件 I/O有帮助的 trait。类似于 Rust 有一个通用的 prelude 来自动引入特定内容,`std::io` 也有其自己的 prelude 来引入处理 I/O 时所需的通用内容。不同于默认的 prelude必须显式 `use` 位于 `std::io` 中的 prelude。
`main` 中,我们增加了三点内容:第一,通过传递变量 `filename` 的值调用 `File::open` 函数来获取文件的可变句柄。创建了叫做 `contents` 的变量并将其设置为一个可变的,空的 `String`。它将会存放之后读取的文件的内容。第三,对文件句柄调用 `read_to_string` 并传递 `contents` 的可变引用作为参数。 `main` 中,我们增加了三点内容:第一,通过传递变量 `filename` 的值调用 `File::open` 函数来获取文件的可变句柄。创建了叫做 `contents` 的变量并将其设置为一个可变的,空的 `String`。它将会存放之后读取的文件的内容。第三,对文件句柄调用 `read_to_string` 并传递 `contents` 的可变引用作为参数。

@ -22,7 +22,7 @@ Rust 尝试缓和使用线程的负面影响。不过在多线程上下文中编
在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。 在当前上下文中,**运行时** 代表二进制文件中包含的由语言自身提供的代码。这些代码根据语言的不同可大可小,不过任何非汇编语言都会有一定数量的运行时代码。为此,通常人们说一个语言 “没有运行时”,一般意味着 “小运行时”。更小的运行时拥有更少的功能不过其优势在于更小的二进制输出,这使其易于在更多上下文中与其他语言相结合。虽然很多语言觉得增加运行时来换取更多功能没有什么问题,但是 Rust 需要做到几乎没有运行时,同时为了保持高性能必需能够调用 C 语言,这点也是不能妥协的。
绿色线程的 M:N 模型更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。因为 Rust 是如此底层的语言,所以有相应的 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本。 绿色线程的 M:N 模型更大的语言运行时来管理这些线程。为此Rust 标准库只提供了 1:1 线程模型实现。Rust 是足够底层的语言,所以有相应的 crate 实现了 M:N 线程模型,如果你宁愿牺牲性能来换取例如更好的线程运行控制和更低的上下文切换成本。
现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。 现在我们明白了 Rust 中的线程是如何定义的,让我们开始探索如何使用标准库提供的线程相关的 API 吧。

@ -12,7 +12,7 @@ Rust 打破了这些障碍,其消除了旧的缺陷并提供了伴你一路同
已经在从事编写底层代码的程序员可以使用 Rust 来提升抱负。例如,在 Rust 中引入并行是相对较为低风险的操作:编译器会为你捕获经典的错误。同时你可以自信的采取更为激进的优化,而不会意外引入崩溃或漏洞。 已经在从事编写底层代码的程序员可以使用 Rust 来提升抱负。例如,在 Rust 中引入并行是相对较为低风险的操作:编译器会为你捕获经典的错误。同时你可以自信的采取更为激进的优化,而不会意外引入崩溃或漏洞。
另一方面 Rust 并不局限于底层系统编程。其表现力与工程学足以愉快的编写 CLI 应用、web server 和很多其他类型的代码 —— 在本书之后你会找到所有这些场景的简单示例。使用 Rust 你学习的技能可以从一个领域延伸到另一个领域;你可以学习 Rust 来编写 web 应用,接着将同样的技能应用到你的 Raspberry Pi 上。 另一方面 Rust 并不局限于底层系统编程。其表现力与工程学足以愉快的编写 CLI 应用、web server 和很多其他类型的代码 —— 在本书之后你会找到所有这些场景的简单示例。使用 Rust 你学习的技能可以从一个领域延伸到另一个领域;你可以学习 Rust 来编写 web 应用,接着将同样的技能应用到你的 Raspberry Pi(树莓派)上。
本书全面拥抱 Rust 授权于用户的潜力。其内容平易近人,致力于帮助你不仅仅提升 Rust 的知识,并且提升你作为程序员整体的理解与自信。那么让我们准备好深入学习 Rust吧打开新世界的大门 :) —— 欢迎加入 Rust 社区! 本书全面拥抱 Rust 授权于用户的潜力。其内容平易近人,致力于帮助你不仅仅提升 Rust 的知识,并且提升你作为程序员整体的理解与自信。那么让我们准备好深入学习 Rust吧打开新世界的大门 :) —— 欢迎加入 Rust 社区!

Loading…
Cancel
Save