fix: fix typos and punctuation errors 4

pull/1410/head
Ellery Zhang 8 months ago
parent 9d4bb86262
commit f26e3723a9

@ -187,13 +187,13 @@ let score: Option<&i32> = scores.get(&team_name);
- `get` 方法返回一个 `Option<&i32>` 类型:当查询不到时,会返回一个 `None`,查询到时返回 `Some(&i32)` - `get` 方法返回一个 `Option<&i32>` 类型:当查询不到时,会返回一个 `None`,查询到时返回 `Some(&i32)`
- `&i32` 是对 `HashMap` 中值的借用,如果不使用借用,可能会发生所有权的转移 - `&i32` 是对 `HashMap` 中值的借用,如果不使用借用,可能会发生所有权的转移
还可以继续拓展下,上面的代码中,如果我们想直接获得值类型的 `score` 该怎么办,答案简约但不简单: 还可以继续拓展下,上面的代码中,如果我们想直接获得值类型的 `score` 该怎么办,答案简约但不简单
```rust ```rust
let score: i32 = scores.get(&team_name).copied().unwrap_or(0); let score: i32 = scores.get(&team_name).copied().unwrap_or(0);
``` ```
这里留给大家一个小作业: 去官方文档中查询下 `Option``copied` 方法和 `unwrap_or` 方法的含义及该如何使用。 这里留给大家一个小作业去官方文档中查询下 `Option``copied` 方法和 `unwrap_or` 方法的含义及该如何使用。
还可以通过循环的方式依次遍历 `KV` 对: 还可以通过循环的方式依次遍历 `KV` 对:

@ -168,7 +168,7 @@ pub mod compute;
## 文档测试(Doc Test) ## 文档测试(Doc Test)
相信读者之前都写过单元测试用例,其中一个很蛋疼的问题就是,随着代码的进化,单元测试用例经常会失效,过段时间后(为何是过段时间?应该这么问,有几个开发喜欢写测试用例 =,=),你发现需要连续修改不少处代码,才能让测试重新工作起来。然而,在 Rust 中,大可不必。 相信读者之前都写过单元测试用例,其中一个很蛋疼的问题就是,随着代码的进化,单元测试用例经常会失效,过段时间后(为何是过段时间?应该这么问,有几个开发喜欢写测试用例 =,=,你发现需要连续修改不少处代码,才能让测试重新工作起来。然而,在 Rust 中,大可不必。
在之前的 `add_one` 中,我们写的示例代码非常像是一个单元测试的用例,这是偶然吗?并不是。因为 Rust 允许我们在文档注释中写单元测试用例!方法就如同之前做的: 在之前的 `add_one` 中,我们写的示例代码非常像是一个单元测试的用例,这是偶然吗?并不是。因为 Rust 允许我们在文档注释中写单元测试用例!方法就如同之前做的:

@ -9,7 +9,7 @@
## 定义 ## 定义
其实项目 `Package` 和包 `Crate` 很容易被搞混,甚至在很多书中,这两者都是不分的,但是由于官方对此做了明确的区分,因此我们会在本章节中试图(挣扎着)理清这个概念。 其实项目 `Package` 和包 `Crate` 很容易被搞混,甚至在很多书中,这两者都是不分的,但是由于官方对此做了明确的区分,因此我们会在本章节中试图(挣扎着)理清这个概念。
#### 包 Crate #### 包 Crate

@ -38,7 +38,7 @@ mod front_of_house {
## 模块树 ## 模块树
在[上一节](https://course.rs/basic/crate-module/crate.html)中,我们提到过 `src/main.rs``src/lib.rs` 被称为包根(crate root),这个奇葩名称的来源(我不想承认是自己翻译水平太烂-,-)是由于这两个文件的内容形成了一个模块 `crate`,该模块位于包的树形结构(由模块组成的树形结构)的根部: 在[上一节](https://course.rs/basic/crate-module/crate.html)中,我们提到过 `src/main.rs``src/lib.rs` 被称为包根(crate root),这个奇葩名称的来源(我不想承认是自己翻译水平太烂-,-是由于这两个文件的内容形成了一个模块 `crate`,该模块位于包的树形结构(由模块组成的树形结构)的根部:
```console ```console
crate crate
@ -156,7 +156,7 @@ crate
## 代码可见性 ## 代码可见性
让我们运行下面(之前)的代码: 让我们运行下面(之前)的代码:
```rust ```rust
mod front_of_house { mod front_of_house {
@ -184,7 +184,7 @@ error[E0603]: module `hosting` is private
| ^^^^^^^ private module | ^^^^^^^ private module
``` ```
错误信息很清晰:`hosting` 模块是私有的,无法在包根进行访问,那么为何 `front_of_house` 模块就可以访问?因为它和 `eat_at_restaurant` 同属于一个包根作用域内,同一个模块内的代码自然不存在私有化问题(所以我们之前章节的代码都没有报过这个错误!) 错误信息很清晰:`hosting` 模块是私有的,无法在包根进行访问,那么为何 `front_of_house` 模块就可以访问?因为它和 `eat_at_restaurant` 同属于一个包根作用域内,同一个模块内的代码自然不存在私有化问题(所以我们之前章节的代码都没有报过这个错误!)
模块不仅仅对于组织代码很有用,它还能定义代码的私有化边界:在这个边界内,什么内容能让外界看到,什么内容不能,都有很明确的定义。因此,如果希望让函数或者结构体等类型变成私有化的,可以使用模块。 模块不仅仅对于组织代码很有用,它还能定义代码的私有化边界:在这个边界内,什么内容能让外界看到,什么内容不能,都有很明确的定义。因此,如果希望让函数或者结构体等类型变成私有化的,可以使用模块。
@ -255,7 +255,7 @@ mod back_of_house {
} }
``` ```
嗯,我们的小餐馆又完善了,终于有厨房了!看来第一个客人也快可以有了。。。在厨房模块中,使用 `super::serve_order` 语法,调用了父模块(包根)中的 `serve_order` 函数。 嗯,我们的小餐馆又完善了,终于有厨房了!看来第一个客人也快可以有了。。。在厨房模块中,使用 `super::serve_order` 语法,调用了父模块(包根)中的 `serve_order` 函数。
那么你可能会问,为何不使用 `crate::serve_order` 的方式?额,其实也可以,不过如果你确定未来这种层级关系不会改变,那么 `super::serve_order` 的方式会更稳定,未来就算它们都不在包根了,依然无需修改引用路径。所以路径的选用,往往还是取决于场景,以及未来代码的可能走向。 那么你可能会问,为何不使用 `crate::serve_order` 的方式?额,其实也可以,不过如果你确定未来这种层级关系不会改变,那么 `super::serve_order` 的方式会更稳定,未来就算它们都不在包根了,依然无需修改引用路径。所以路径的选用,往往还是取决于场景,以及未来代码的可能走向。

@ -70,7 +70,7 @@ fn main() {
} }
``` ```
其实严格来说,对于引用方式并没有需要遵守的惯例,主要还是取决于你的喜好,不过我们建议:**优先使用最细粒度(引入函数、结构体等)的引用方式,如果引起了某种麻烦(例如前面两种情况),再使用引入模块的方式**。 其实严格来说,对于引用方式并没有需要遵守的惯例,主要还是取决于你的喜好,不过我们建议:**优先使用最细粒度(引入函数、结构体等)的引用方式,如果引起了某种麻烦(例如前面两种情况),再使用引入模块的方式**。
## 避免同名引用 ## 避免同名引用
@ -228,7 +228,7 @@ fn main() {
} }
``` ```
以上代码中,`std::collection::HashMap` 被 `*` 引入到当前作用域,但是由于存在另一个同名的结构体,因此 `HashMap::new` 根本不存在,因为对于编译器来说,本地同名类型的优先级更高。 以上代码中,`std::collections::HashMap` 被 `*` 引入到当前作用域,但是由于存在另一个同名的结构体,因此 `HashMap::new` 根本不存在,因为对于编译器来说,本地同名类型的优先级更高。
在实际项目中,这种引用方式往往用于快速写测试代码,它可以把所有东西一次性引入到 `tests` 模块中。 在实际项目中,这种引用方式往往用于快速写测试代码,它可以把所有东西一次性引入到 `tests` 模块中。

@ -195,7 +195,7 @@ fn main() {
## 位置参数 ## 位置参数
除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从 0 开始) 除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从 0 开始)
```rust ```rust
fn main() { fn main() {

@ -7,7 +7,7 @@
- 就像编译器大部分时候可以自动推导类型 <-> 一样,编译器大多数时候也可以自动推导生命周期 - 就像编译器大部分时候可以自动推导类型 <-> 一样,编译器大多数时候也可以自动推导生命周期
- 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期 - 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期
Rust 生命周期之所以难,是因为这个概念对于我们来说是全新的,没有其它编程语言的经验可以借鉴。当你觉得难的时候,不用过于担心,这个难对于所有人都是平等的,多点付出就能早点解决此拦路虎,同时本书也会尽力帮助大家减少学习难度(生命周期很可能是 Rust 中最难的部分) Rust 生命周期之所以难,是因为这个概念对于我们来说是全新的,没有其它编程语言的经验可以借鉴。当你觉得难的时候,不用过于担心,这个难对于所有人都是平等的,多点付出就能早点解决此拦路虎,同时本书也会尽力帮助大家减少学习难度(生命周期很可能是 Rust 中最难的部分)
## 悬垂指针和生命周期 ## 悬垂指针和生命周期
@ -130,7 +130,7 @@ help: consider introducing a named lifetime parameter // 考虑引入一个生
不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回 `x`,另一个分支返回 `y`...这可咋办?先来分析下。 不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回 `x`,另一个分支返回 `y`...这可咋办?先来分析下。
我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是 `if` 还是 `else` 被执行,无从得知。其次,传入引用的具体生命周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道 `x``y` 的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明) 我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是 `if` 还是 `else` 被执行,无从得知。其次,传入引用的具体生命周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道 `x``y` 的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)
因此,这时就回到了文章开头说的内容:在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。 因此,这时就回到了文章开头说的内容:在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。
@ -175,9 +175,9 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
需要注意的点如下: 需要注意的点如下:
- 和泛型一样,使用生命周期参数,需要先声明 `<'a>` - 和泛型一样,使用生命周期参数,需要先声明 `<'a>`
- `x`、`y` 和返回值至少活得和 `'a` 一样久(因为返回值要么是 `x`,要么是 `y`) - `x`、`y` 和返回值至少活得和 `'a` 一样久(因为返回值要么是 `x`,要么是 `y`
该函数签名表明对于某些生命周期 `'a`,函数的两个参数都至少跟 `'a` 活得一样久,同时函数的返回引用也至少跟 `'a` 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 `'a`,但是实际上这两个参数的真实生命周期可能是不一样的(生命周期 `'a` 不代表生命周期等于 `'a`,而是大于等于 `'a`) 该函数签名表明对于某些生命周期 `'a`,函数的两个参数都至少跟 `'a` 活得一样久,同时函数的返回引用也至少跟 `'a` 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 `'a`,但是实际上这两个参数的真实生命周期可能是不一样的生命周期 `'a` 不代表生命周期等于 `'a`,而是大于等于 `'a`
回忆下“鲁迅”说的话,再参考上面的内容,可以得出:**在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过**。 回忆下“鲁迅”说的话,再参考上面的内容,可以得出:**在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过**。
@ -245,7 +245,7 @@ error[E0597]: `string2` does not live long enough
#### 深入思考生命周期标注 #### 深入思考生命周期标注
使用生命周期的方式往往取决于函数的功能,例如之前的 `longest` 函数,如果它永远只返回第一个参数 `x`,生命周期的标注该如何修改(该例子就是上面的小练习结果之一)? 使用生命周期的方式往往取决于函数的功能,例如之前的 `longest` 函数,如果它永远只返回第一个参数 `x`,生命周期的标注该如何修改(该例子就是上面的小练习结果之一)?
```rust ```rust
fn longest<'a>(x: &'a str, y: &str) -> &'a str { fn longest<'a>(x: &'a str, y: &str) -> &'a str {
@ -408,7 +408,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
例如一个引用参数的函数就有一个生命周期标注: `fn foo<'a>(x: &'a i32)`,两个引用参数的有两个生命周期标注:`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, 依此类推。 例如一个引用参数的函数就有一个生命周期标注: `fn foo<'a>(x: &'a i32)`,两个引用参数的有两个生命周期标注:`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, 依此类推。
2. **若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期 2. **若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期
例如函数 `fn foo(x: &i32) -> &i32``x` 参数的生命周期会被自动赋给返回值 `&i32`,因此该函数等同于 `fn foo<'a>(x: &'a i32) -> &'a i32` 例如函数 `fn foo(x: &i32) -> &i32``x` 参数的生命周期会被自动赋给返回值 `&i32`,因此该函数等同于 `fn foo<'a>(x: &'a i32) -> &'a i32`

@ -70,7 +70,7 @@ fn main() {
该例子定义了一个 `Rectangle` 结构体,并且在其上定义了一个 `area` 方法,用于计算该矩形的面积。 该例子定义了一个 `Rectangle` 结构体,并且在其上定义了一个 `area` 方法,用于计算该矩形的面积。
`impl Rectangle {}` 表示为 `Rectangle` 实现方法(`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。 `impl Rectangle {}` 表示为 `Rectangle` 实现方法`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。
#### self、&self 和 &mut self #### self、&self 和 &mut self

@ -4,7 +4,7 @@
## panic! 与不可恢复错误 ## panic! 与不可恢复错误
上面的问题在真实场景会经常遇到,其实处理起来挺复杂的,让我们先做一个假设:文件读取操作发生在系统启动阶段。那么可以轻易得出一个结论,一旦文件读取失败,那么系统启动也将失败,这意味着该失败是不可恢复的错误,无论是因为文件不存在还是操作系统硬盘的问题,这些只是错误的原因不同,但是归根到底都是不可恢复的错误(梳理清楚当前场景的错误类型非常重要) 上面的问题在真实场景会经常遇到,其实处理起来挺复杂的,让我们先做一个假设:文件读取操作发生在系统启动阶段。那么可以轻易得出一个结论,一旦文件读取失败,那么系统启动也将失败,这意味着该失败是不可恢复的错误,无论是因为文件不存在还是操作系统硬盘的问题,这些只是错误的原因不同,但是归根到底都是不可恢复的错误(梳理清楚当前场景的错误类型非常重要)
对于这些严重到影响程序运行的错误,触发 `panic` 是很好的解决方式。在 Rust 中触发 `panic` 有两种方式:被动触发和主动调用,下面依次来看看。 对于这些严重到影响程序运行的错误,触发 `panic` 是很好的解决方式。在 Rust 中触发 `panic` 有两种方式:被动触发和主动调用,下面依次来看看。
@ -80,7 +80,7 @@ fn main() {
} }
``` ```
上面的代码很简单,数组只有 `3` 个元素,我们却尝试去访问它的第 `100` 号元素(数组索引从 `0` 开始),那自然会崩溃。 上面的代码很简单,数组只有 `3` 个元素,我们却尝试去访问它的第 `100` 号元素(数组索引从 `0` 开始),那自然会崩溃。
我们的读者里不乏正义之士,此时肯定要质疑,一个简单的数组越界访问,为何要直接让程序崩溃?是不是有些小题大作了? 我们的读者里不乏正义之士,此时肯定要质疑,一个简单的数组越界访问,为何要直接让程序崩溃?是不是有些小题大作了?
@ -117,7 +117,7 @@ stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace. note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
``` ```
上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。因为咱们的 `main` 函数基本是最先调用的函数了,所以排在了倒数第二位,还有一个关注点,排在最顶部最后一个调用的函数是 `rust_begin_unwind`,该函数的目的就是进行栈展开,呈现这些列表信息给我们。 上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。因为咱们的 `main` 函数基本是最先调用的函数了,所以排在了倒数第二位,还有一个关注点,排在最顶部最后一个调用的函数是 `rust_begin_unwind`,该函数的目的就是进行栈展开,呈现这些列表信息给我们。
要获取到栈回溯信息,你还需要开启 `debug` 标志,该标志在使用 `cargo run` 或者 `cargo build` 时自动开启(这两个操作默认是 `Debug` 运行方式)。同时,栈展开信息在不同操作系统或者 Rust 版本上也有所不同。 要获取到栈回溯信息,你还需要开启 `debug` 标志,该标志在使用 `cargo run` 或者 `cargo build` 时自动开启(这两个操作默认是 `Debug` 运行方式)。同时,栈展开信息在不同操作系统或者 Rust 版本上也有所不同。

@ -55,7 +55,7 @@ error[E0308]: mismatched types
别慌,其实很简单,首先 `Result` 本身是定义在 `std::result` 中的,但是因为 `Result` 很常用,所以就被包含在了 [`prelude`](https://course.rs/appendix/prelude.html) 中(将常用的东东提前引入到当前作用域内),因此无需手动引入 `std::result::Result`,那么返回类型可以简化为 `Result<std::fs::File,std::io::Error>`,你看看是不是很像标准的 `Result<T, E>` 枚举定义?只不过 `T` 被替换成了具体的类型 `std::fs::File`,是一个文件句柄类型,`E` 被替换成 `std::io::Error`,是一个 IO 错误类型。 别慌,其实很简单,首先 `Result` 本身是定义在 `std::result` 中的,但是因为 `Result` 很常用,所以就被包含在了 [`prelude`](https://course.rs/appendix/prelude.html) 中(将常用的东东提前引入到当前作用域内),因此无需手动引入 `std::result::Result`,那么返回类型可以简化为 `Result<std::fs::File,std::io::Error>`,你看看是不是很像标准的 `Result<T, E>` 枚举定义?只不过 `T` 被替换成了具体的类型 `std::fs::File`,是一个文件句柄类型,`E` 被替换成 `std::io::Error`,是一个 IO 错误类型。
这个返回值类型说明 `File::open` 调用如果成功则返回一个可以进行读写的文件句柄,如果失败,则返回一个 IO 错误:文件不存在或者没有访问文件的权限等。总之 `File::open` 需要一个方式告知调用者是成功还是失败,并同时返回具体的文件句柄(成功)或错误信息(失败),万幸的是,这些信息可以通过 `Result` 枚举提供: 这个返回值类型说明 `File::open` 调用如果成功则返回一个可以进行读写的文件句柄,如果失败,则返回一个 IO 错误:文件不存在或者没有访问文件的权限等。总之 `File::open` 需要一个方式告知调用者是成功还是失败,并同时返回具体的文件句柄(成功)或错误信息(失败),万幸的是,这些信息可以通过 `Result` 枚举提供:
```rust ```rust
use std::fs::File; use std::fs::File;
@ -188,7 +188,7 @@ fn read_username_from_file() -> Result<String, io::Error> {
由此可见,该函数将 `io::Error` 的错误往上进行传播,该函数的调用者最终会对 `Result<String,io::Error>` 进行再处理,至于怎么处理就是调用者的事,如果是错误,它可以选择继续向上传播错误,也可以直接 `panic`,亦或将具体的错误原因包装后写入 socket 中呈现给终端用户。 由此可见,该函数将 `io::Error` 的错误往上进行传播,该函数的调用者最终会对 `Result<String,io::Error>` 进行再处理,至于怎么处理就是调用者的事,如果是错误,它可以选择继续向上传播错误,也可以直接 `panic`,亦或将具体的错误原因包装后写入 socket 中呈现给终端用户。
但是上面的代码也有自己的问题,那就是太长了(优秀的程序员身上的优点极多,其中最大的优点就是*懒*),我自认为也有那么一点点优秀,因此见不得这么啰嗦的代码,下面咱们来讲讲如何简化它。 但是上面的代码也有自己的问题,那就是太长了(优秀的程序员身上的优点极多,其中最大的优点就是*懒*,我自认为也有那么一点点优秀,因此见不得这么啰嗦的代码,下面咱们来讲讲如何简化它。
### 传播界的大明星: ? ### 传播界的大明星: ?
@ -311,7 +311,7 @@ fn last_char_of_first_line(text: &str) -> Option<char> {
} }
``` ```
上面代码展示了在链式调用中使用 `?` 提前返回 `None` 的用法, `.next` 方法返回的是 `Option` 类型:如果返回 `Some(&str)`,那么继续调用 `chars` 方法,如果返回 `None`,则直接从整个函数中返回 `None`,不再继续进行链式调用。 上面代码展示了在链式调用中使用 `?` 提前返回 `None` 的用法, `.next` 方法返回的是 `Option` 类型:如果返回 `Some(&str)`,那么继续调用 `chars` 方法如果返回 `None`,则直接从整个函数中返回 `None`,不再继续进行链式调用。
#### 新手用 ? 常会犯的错误 #### 新手用 ? 常会犯的错误

@ -47,7 +47,7 @@ fn main() {
## 泛型详解 ## 泛型详解
上面代码的 `T` 就是**泛型参数**,实际上在 Rust 中,泛型参数的名称你可以任意起,但是出于惯例,我们都用 `T` ( `T``type` 的首字母)来作为首选,这个名称越短越好,除非需要表达含义,否则一个字母是最完美的。 上面代码的 `T` 就是**泛型参数**,实际上在 Rust 中,泛型参数的名称你可以任意起,但是出于惯例,我们都用 `T` `T` 是 `type` 的首字母)来作为首选,这个名称越短越好,除非需要表达含义,否则一个字母是最完美的。
使用泛型参数,有一个先决条件,必需在使用前对其进行声明: 使用泛型参数,有一个先决条件,必需在使用前对其进行声明:

@ -1,6 +1,6 @@
# 特征对象 # 特征对象
在上一节中有一段代码无法通过编译: 在上一节中有一段代码无法通过编译
```rust ```rust
fn returns_summarizable(switch: bool) -> impl Summary { fn returns_summarizable(switch: bool) -> impl Summary {

@ -1,6 +1,6 @@
# 特征 Trait # 特征 Trait
如果我们想定义一个文件系统,那么把该系统跟底层存储解耦是很重要的。文件操作主要包含四个:`open` 、`write`、`read`、`close`这些操作可以发生在硬盘可以发生在内存还可以发生在网络IO甚至...我实在编不下去了,大家来帮帮我)。总之如果你要为每一种情况都单独实现一套代码,那这种实现将过于繁杂,而且也没那个必要。 如果我们想定义一个文件系统,那么把该系统跟底层存储解耦是很重要的。文件操作主要包含四个:`open` 、`write`、`read`、`close`,这些操作可以发生在硬盘,可以发生在内存,还可以发生在网络 IO 甚至(...我实在编不下去了,大家来帮帮我)。总之如果你要为每一种情况都单独实现一套代码,那这种实现将过于繁杂,而且也没那个必要。
要解决上述问题,需要把这些行为抽象出来,就要使用 Rust 中的特征 `trait` 概念。可能你是第一次听说这个名词,但是不要怕,如果学过其他语言,那么大概率你听说过接口,没错,特征跟接口很类似。 要解决上述问题,需要把这些行为抽象出来,就要使用 Rust 中的特征 `trait` 概念。可能你是第一次听说这个名词,但是不要怕,如果学过其他语言,那么大概率你听说过接口,没错,特征跟接口很类似。
@ -273,7 +273,7 @@ impl<T: Display + PartialOrd> Pair<T> {
`cmp_display` 方法,并不是所有的 `Pair<T>` 结构体对象都可以拥有,只有 `T` 同时实现了 `Display + PartialOrd``Pair<T>` 才可以拥有此方法。 `cmp_display` 方法,并不是所有的 `Pair<T>` 结构体对象都可以拥有,只有 `T` 同时实现了 `Display + PartialOrd``Pair<T>` 才可以拥有此方法。
该函数可读性会更好,因为泛型参数、参数、返回值都在一起,可以快速的阅读,同时每个泛型参数的特征也在新的代码行中通过**特征约束**进行了约束。 该函数可读性会更好,因为泛型参数、参数、返回值都在一起,可以快速的阅读,同时每个泛型参数的特征也在新的代码行中通过**特征约束**进行了约束。
**也可以有条件地实现特征**, 例如,标准库为任何实现了 `Display` 特征的类型实现了 `ToString` 特征: **也可以有条件地实现特征**例如,标准库为任何实现了 `Display` 特征的类型实现了 `ToString` 特征:
```rust ```rust
impl<T: Display> ToString for T { impl<T: Display> ToString for T {
@ -304,7 +304,7 @@ fn returns_summarizable() -> impl Summary {
因为 `Weibo` 实现了 `Summary`,因此这里可以用它来作为返回值。要注意的是,虽然我们知道这里是一个 `Weibo` 类型,但是对于 `returns_summarizable` 的调用者而言,他只知道返回了一个实现了 `Summary` 特征的对象,但是并不知道返回了一个 `Weibo` 类型。 因为 `Weibo` 实现了 `Summary`,因此这里可以用它来作为返回值。要注意的是,虽然我们知道这里是一个 `Weibo` 类型,但是对于 `returns_summarizable` 的调用者而言,他只知道返回了一个实现了 `Summary` 特征的对象,但是并不知道返回了一个 `Weibo` 类型。
这种 `impl Trait` 形式的返回值,在一种场景下非常非常有用,那就是返回的真实类型非常复杂,你不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型),此时就可以用 `impl Trait` 的方式简单返回。例如,闭包和迭代器就是很复杂,只有编译器才知道那玩意的真实类型,如果让你写出来它们的具体类型,估计内心有一万只草泥马奔腾,好在你可以用 `impl Iterator` 来告诉调用者,返回了一个迭代器,因为所有迭代器都会实现 `Iterator` 特征。 这种 `impl Trait` 形式的返回值,在一种场景下非常非常有用,那就是返回的真实类型非常复杂,你不知道该怎么声明时(毕竟 Rust 要求你必须标出所有的类型),此时就可以用 `impl Trait` 的方式简单返回。例如,闭包和迭代器就是很复杂,只有编译器才知道那玩意的真实类型,如果让你写出来它们的具体类型,估计内心有一万只草泥马奔腾,好在你可以用 `impl Iterator` 来告诉调用者,返回了一个迭代器,因为所有迭代器都会实现 `Iterator` 特征。
但是这种返回值方式有一个很大的限制:只能有一个具体的类型,例如: 但是这种返回值方式有一个很大的限制:只能有一个具体的类型,例如:

Loading…
Cancel
Save