Merge pull request #603 from zongzi531/hotfix/abspath

fix: link url to abs path
pull/639/head
Sunface 3 years ago committed by GitHub
commit e8fb513fae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -106,7 +106,7 @@ fn main() {
}
```
报错跟之前无二: ``` `*mut u8` cannot be sent between threads safely```, 但是有一个问题,我们无法为其直接实现`Send`特征,好在可以用[`newtype`类型](../into-types/custom-type.md#newtype) :`struct MyBox(*mut u8);`。
报错跟之前无二: ``` `*mut u8` cannot be sent between threads safely```, 但是有一个问题,我们无法为其直接实现`Send`特征,好在可以用[`newtype`类型](https://course.rs/advance/into-types/custom-type.html#newtype) :`struct MyBox(*mut u8);`。
还记得之前的规则吗:复合类型中有一个成员没实现`Send`,该复合类型就不是`Send`,因此我们需要手动为它实现:

@ -117,7 +117,7 @@ fn main() {
}
```
由于子线程需要通过`move`拿走锁的所有权,因此我们需要使用多所有权来保证每个线程都拿到数据的独立所有权,恰好智能指针[`Rc<T>`](../smart-pointer/rc-arc.md)可以做到(**上面代码会报错**!具体往下看,别跳过-, -)。
由于子线程需要通过`move`拿走锁的所有权,因此我们需要使用多所有权来保证每个线程都拿到数据的独立所有权,恰好智能指针[`Rc<T>`](https://course.rs/advance/smart-pointer/rc-arc.html)可以做到(**上面代码会报错**!具体往下看,别跳过-, -)。
以上代码实现了在多线程中计数的功能,由于多个线程都需要去修改该计数器,因此我们需要使用锁来保证同一时间只有一个线程可以修改计数器,否则会导致脏数据:想象一下 A 线程和 B 线程同时拿到计数器,获取了当前值`1`, 并且同时对其进行了修改,最后值变成`2`,你会不会在风中凌乱?毕竟正确的值是`3`,因为两个线程各自加 1。
@ -151,7 +151,7 @@ error[E0277]: `Rc<Mutex<i32>>` cannot be sent between threads safely
##### 多线程安全的 Arc<T>
好在,我们有`Arc<T>`,得益于它的[内部计数器](../smart-pointer/rc-arc.md#多线程无力的rc)是多线程安全的,因此可以在多线程环境中使用:
好在,我们有`Arc<T>`,得益于它的[内部计数器](https://course.rs/advance/smart-pointer/rc-arc.html#多线程无力的rc)是多线程安全的,因此可以在多线程环境中使用:
```rust
use std::sync::{Arc, Mutex};
@ -187,7 +187,7 @@ Result: 10
#### 内部可变性
在之前章节,我们提到过[内部可变性](../smart-pointer/cell-refcell.md#内部可变性),其中`Rc<T>`和`RefCell<T>`的结合,可以实现单线程的内部可变性。
在之前章节,我们提到过[内部可变性](https://course.rs/advance/smart-pointer/cell-refcell.html#内部可变性),其中`Rc<T>`和`RefCell<T>`的结合,可以实现单线程的内部可变性。
现在我们又有了新的武器,由于`Mutex<T>`可以支持修改内部数据,当结合`Arc<T>`一起使用时,可以实现多线程的内部可变性。
@ -204,7 +204,7 @@ Result: 10
正因为这种困难性,导致很多用户都热衷于使用消息传递的方式来实现同步,例如 Go 语言直接把`channel`内置在语言特性中,甚至还有无锁的语言,例如`erlang`,完全使用`Actor`模型,依赖消息传递来完成共享和同步。幸好 Rust 的类型系统、所有权机制、智能指针等可以很好的帮助我们减轻使用锁时的负担。
另一个值的注意的是在使用`Mutex<T>`时Rust 无法帮我们避免所有的逻辑错误,例如在之前章节,我们提到过使用`Rc<T>`可能会导致[循环引用的问题](../circle-self-ref/circle-reference.md)。类似的,`Mutex<T>`也存在使用上的风险,例如创建死锁(deadlock):当一个操作试图锁住两个资源,然后两个线程各自获取其中一个锁,并试图获取另一个锁时,就会造成死锁。
另一个值的注意的是在使用`Mutex<T>`时Rust 无法帮我们避免所有的逻辑错误,例如在之前章节,我们提到过使用`Rc<T>`可能会导致[循环引用的问题](https://course.rs/advance/circle-self-ref/circle-reference.html)。类似的,`Mutex<T>`也存在使用上的风险,例如创建死锁(deadlock):当一个操作试图锁住两个资源,然后两个线程各自获取其中一个锁,并试图获取另一个锁时,就会造成死锁。
## 死锁

@ -6,7 +6,7 @@
由于原子操作是通过指令提供的支持,因此它的性能相比锁和消息传递会好很多。相比较于锁而言,原子类型不需要开发者处理加锁和释放锁的问题,同时支持修改,读取等操作,还具备较高的并发性能,几乎所有的语言都支持原子类型。
可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了`CAS`循环,当大量的冲突发生时,该等待还是得[等待](./thread.md#多线程的开销)!但是总归比锁要好。
可以看出原子类型是无锁类型,但是无锁不代表无需等待,因为原子类型内部使用了`CAS`循环,当大量的冲突发生时,该等待还是得[等待](https://course.rs/advance/concurrency-with-threads/thread.html#多线程的开销)!但是总归比锁要好。
> CAS 全称是 Compare and swap, 它通过一条指令读取指定的内存地址,然后判断其中的值是否等于给定的前置值,如果相等,则将其修改为新的值

@ -516,7 +516,7 @@ fn main() {
## 总结
[Rust 的线程模型](./intro.md)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
[Rust 的线程模型](https://course.rs/advance/concurrency-with-threads/intro.html)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
我们可以使用 `thread::spawn` 来创建线程,创建出的多个线程之间并不存在执行顺序关系,因此代码逻辑千万不要依赖于线程间的执行顺序。

@ -708,7 +708,7 @@ help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of t
嗯,编译器提示我们加一个 `impl` 关键字,哦,这样一说,读者可能就想起来了,`impl Trait` 可以用来返回一个实现了指定特征的类型,那么这里 `impl Fn(i32) -> i32` 的返回值形式,说明我们要返回一个闭包类型,它实现了 `Fn(i32) -> i32` 特征。
完美解决,但是,在[特征](../../basic/trait/trait.md)那一章,我们提到过,`impl Trait` 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:
完美解决,但是,在[特征](https://course.rs/basic/trait/trait.html)那一章,我们提到过,`impl Trait` 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:
```rust
fn factory(x:i32) -> impl Fn(i32) -> i32 {

@ -71,7 +71,7 @@ fn main() {
}
```
关于原子类型的讲解看[这篇文章](./concurrency-with-threads/sync2.md)
关于原子类型的讲解看[这篇文章](https://course.rs/advance/concurrency-with-threads/sync2.html)
#### 示例:全局 ID 生成器

@ -175,7 +175,7 @@ back_to_enum! {
**这个方法原则上并不推荐,但是有其存在的意义,如果要使用,你需要清晰的知道自己为什么使用**。
在之前的类型转换章节,我们提到过非常邪恶的[`transmute`转换](../../basic/converse.md#变形记transmutes),其实,当你知道数值一定不会超过枚举的范围时(例如枚举成员对应 123传入的整数也在这个范围内),就可以使用这个方法完成变形。
在之前的类型转换章节,我们提到过非常邪恶的[`transmute`转换](https://course.rs/basic/converse.html#变形记transmutes),其实,当你知道数值一定不会超过枚举的范围时(例如枚举成员对应 123传入的整数也在这个范围内),就可以使用这个方法完成变形。
> 最好使用#[repr(..)]来控制底层类型的大小,免得本来需要 i32结果传入 i64最终内存无法对齐产生奇怪的结果

@ -256,7 +256,7 @@ let closure_slision = |x: &i32| -> &i32 { x };
## NLL (Non-Lexical Lifetime)
之前我们在[引用与借用](../../basic/ownership/borrowing.md#NLL)那一章其实有讲到过这个概念,简单来说就是:**引用的生命周期正常来说应该从借用开始一直持续到作用域结束**,但是这种规则会让多引用共存的情况变得更复杂:
之前我们在[引用与借用](https://course.rs/basic/ownership/borrowing.html#NLL)那一章其实有讲到过这个概念,简单来说就是:**引用的生命周期正常来说应该从借用开始一直持续到作用域结束**,但是这种规则会让多引用共存的情况变得更复杂:
```rust
fn main() {

@ -156,7 +156,7 @@ fn main() {
Bingo完美拿走了所有权而且这种实现保证了后续的使用必定会导致编译错误因此非常安全
细心的同学可能已经注意到,这里直接调用了 `drop` 函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](../../appendix/prelude.md)里。
细心的同学可能已经注意到,这里直接调用了 `drop` 函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](https://course.rs/appendix/prelude.html)里。
## Drop 使用场景

@ -16,7 +16,7 @@
一个无法被派生的特征例子是为终端用户处理格式化的 `Display` 。你应该时常考虑使用合适的方法来为终端用户显示一个类型。终端用户应该看到类型的什么部分他们会找出相关部分吗对他们来说最关心的数据格式是什么样的Rust 编译器没有这样的洞察力,因此无法为你提供合适的默认行为。
本附录所提供的可派生特征列表其实并不全面:库可以为其内部的特征实现 `derive` ,因此除了本文列出的标准库 `derive` 之外,还有很多很多其它库的 `derive` 。实现 `derive` 涉及到过程宏的应用,这在[宏章节](../advance/macro.md)中有介绍。
本附录所提供的可派生特征列表其实并不全面:库可以为其内部的特征实现 `derive` ,因此除了本文列出的标准库 `derive` 之外,还有很多很多其它库的 `derive` 。实现 `derive` 涉及到过程宏的应用,这在[宏章节](https://course.rs/advance/macro.html)中有介绍。
### 用于开发者输出的 `Debug`
@ -78,6 +78,6 @@
`Default` 特征会帮你创建一个类型的默认值。 派生 `Default` 意味着自动实现了 `default` 函数。 `default` 函数的派生实现调用了类型每部分的 `default` 函数,这意味着类型中所有的字段也必须实现了 `Default`,这样才能够派生 `Default`
`Default::default` 函数通常结合结构体更新语法一起使用,这在第五章的 [结构体更新语法](../basic/compound-type/struct.md#结构体更新语法) 部分有讨论。可以自定义一个结构体的一小部分字段而剩余字段则使用 `..Default::default()` 设置为默认值。
`Default::default` 函数通常结合结构体更新语法一起使用,这在第五章的 [结构体更新语法](https://course.rs/basic/compound-type/struct.html#结构体更新语法) 部分有讨论。可以自定义一个结构体的一小部分字段而剩余字段则使用 `..Default::default()` 设置为默认值。
例如,当你在 `Option<T>` 实例上使用 `unwrap_or_default` 方法时, `Default` 特征是必须的。如果 `Option<T>``None` 的话, `unwrap_or_default` 方法将返回 `T` 类型的 `Default::default` 的结果。

@ -22,7 +22,7 @@ fn bar() -> impl Future<Output = u8> {
`async` 是懒惰的,直到被执行器 `poll` 或者 `.await` 后才会开始运行,其中后者是最常用的运行 `Future` 的方法。 当 `.await` 被调用时,它会尝试运行 `Future` 直到完成,但是若该 `Future` 进入阻塞,那就会让出当前线程的控制权。当 `Future` 后面准备再一次被运行时(例如从 `socket` 中读取到了数据),执行器会得到通知,并再次运行该 `Future` ,如此循环,直到完成。
以上过程只是一个简述,详细内容在[底层探秘](./future-excuting.md)中已经被深入讲解过,因此这里不再赘述。
以上过程只是一个简述,详细内容在[底层探秘](https://course.rs/async-rust/async/future-excuting.html)中已经被深入讲解过,因此这里不再赘述。
## `async` 的生命周期

@ -18,7 +18,7 @@
由于并发编程在现代社会非常重要因此每个主流语言都对自己的并发模型进行过权衡取舍和精心设计Rust 语言也不例外。下面的列表可以帮助大家理解不同并发模型的取舍:
- **OS 线程**, 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](../advnce/../advance/concurrency-with-threads/concurrency-parallelism.md)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够看。
- **OS 线程**, 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够看。
- **事件驱动(Event driven)**, 这个名词你可能比较陌生,如果说事件驱动常常跟回调( Callback )一起使用,相信大家就恍然大悟了。这种模型性能相当的好,但最大的问题就是存在回调地狱的风险:非线性的控制流和结果处理导致了数据流向和错误传播变得难以掌控,还会导致代码可维护性和可读性的大幅降低,大名鼎鼎的 JS 曾经就存在回调地狱。
- **协程(Coroutines)** 可能是目前最火的并发模型,`Go` 语言的协程设计就非常优秀,这也是 `Go` 语言能够迅速火遍全球的杀手锏之一。协程跟线程类似,无需改变编程模型,同时,它也跟 `async` 类似,可以支持大量的任务并发运行。但协程抽象层次过高,导致用户无法接触到底层的细节,这对于系统编程语言和自定义异步运行时是难以接受的
- **actor 模型**是 erlang 的杀手锏之一,它将所有并发计算分割成一个一个单元,这些单元被称为 `actor` , 单元之间通过消息传递的方式进行通信和数据传递,跟分布式系统的设计理念非常相像。由于 `actor` 模型跟现实很贴近,因此它相对来说更容易实现,但是一旦遇到流控制、失败重试等场景时,就会变得不太好用

@ -154,7 +154,7 @@ enum Recursive {
}
```
这是典型的[动态大小类型](../advance/into-types/sized.md#动态大小类型-dst),它的大小会无限增长,因此编译器会直接报错:
这是典型的[动态大小类型](https://course.rs/advance/into-types/sized.html#动态大小类型-dst),它的大小会无限增长,因此编译器会直接报错:
```shell
error[E0733]: recursion in an `async fn` requires boxing

@ -300,7 +300,7 @@ impl Write for MockTcpStream {
}
```
最后,我们的 mock 需要实现 `Unpin` 特征,表示它可以在内存中安全的移动,具体内容在[前面章节](./pin-unpin.md)有讲。
最后,我们的 mock 需要实现 `Unpin` 特征,表示它可以在内存中安全的移动,具体内容在[前面章节](https://course.rs/async-rust/async/pin-unpin.html)有讲。
```rust
use std::marker::Unpin;

@ -169,7 +169,7 @@ help: to force the async block to take ownership of `v` (and any other
在报错的同时Rust 编译器还给出了相当有帮助的提示:为 `async` 语句块使用 `move` 关键字,这样就能将 `v` 的所有权从 `main` 函数转移到新创建的任务中。
但是 `move` 有一个问题,一个数据只能被一个任务使用,如果想要多个任务使用一个数据,就有些强人所难。不知道还有多少同学记得 [`Arc`](../advance/smart-pointer/rc-arc.md),它可以轻松解决该问题,还是线程安全的。
但是 `move` 有一个问题,一个数据只能被一个任务使用,如果想要多个任务使用一个数据,就有些强人所难。不知道还有多少同学记得 [`Arc`](https://course.rs/advance/smart-pointer/rc-arc.html),它可以轻松解决该问题,还是线程安全的。
在上面的报错中,还有一句很奇怪的信息`function requires argument type to outlive 'static` 函数要求参数类型的生命周期必须比 `'static` 长,问题是 `'static` 已经活得跟整个程序一样久了,难道函数的参数还能活得更久?大家可能会觉得编译器秀逗了,毕竟其它语言编译器也有秀逗的时候:)

@ -168,7 +168,7 @@ error[E0308]: mismatched types // 类型不匹配
| - help: consider removing this semicolon
```
还记得我们在[语句与表达式](./statement-expression.md)中讲过的吗?只有表达式能返回值,而 `;` 结尾的是语句,在 Rust 中,一定要严格区分**表达式**和**语句**的区别,这个在其它语言中往往是被忽视的点。
还记得我们在[语句与表达式](https://course.rs/basic/base-type/statement-expression.html)中讲过的吗?只有表达式能返回值,而 `;` 结尾的是语句,在 Rust 中,一定要严格区分**表达式**和**语句**的区别,这个在其它语言中往往是被忽视的点。
##### 永不返回的函数`!`

@ -55,7 +55,7 @@ error[E0658]: `let` expressions in this position are experimental
```
以上的错误告诉我们 `let` 是语句,不是表达式,因此它不返回值,也就不能给其它变量赋值。但是该错误还透漏了一个重要的信息, `let` 作为表达式已经是试验功能了,也许不久的将来,我们在 [`stable rust`](../../appendix/rust-version.md) 下可以这样使用。
以上的错误告诉我们 `let` 是语句,不是表达式,因此它不返回值,也就不能给其它变量赋值。但是该错误还透漏了一个重要的信息, `let` 作为表达式已经是试验功能了,也许不久的将来,我们在 [`stable rust`](https://course.rs/appendix/rust-version.html) 下可以这样使用。
## 表达式

@ -24,7 +24,7 @@ my_gems.insert("河边捡的误以为是宝石的破石头", 18);
很简单对吧?跟其它语言没有区别,聪明的同学甚至能够猜到该 `HashMap` 的类型:`HashMap<&str,i32>`。
但是还有一点,你可能没有注意,那就是使用 `HashMap` 需要手动通过 `use ...` 从标准库中引入到我们当前的作用域中来,仔细回忆下,之前使用另外两个集合类型 `String``Vec` 时,我们是否有手动引用过?答案是 `No`,因为 `HashMap` 并没有包含在 Rust 的 [`prelude`](../../appendix/prelude.md) 中Rust 为了简化用户使用,提前将最常用的类型自动引入到作用域中)。
但是还有一点,你可能没有注意,那就是使用 `HashMap` 需要手动通过 `use ...` 从标准库中引入到我们当前的作用域中来,仔细回忆下,之前使用另外两个集合类型 `String``Vec` 时,我们是否有手动引用过?答案是 `No`,因为 `HashMap` 并没有包含在 Rust 的 [`prelude`](https://course.rs/appendix/prelude.html) 中Rust 为了简化用户使用,提前将最常用的类型自动引入到作用域中)。
所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,`HashMap` 也是内聚性的,即所有的 `K` 必须拥有同样的类型,`V` 也是如此。
@ -299,7 +299,7 @@ assert_eq!(hash.get(&42), Some(&"the answer"));
> 目前,`HashMap` 使用的哈希函数是 `SipHash`,它的性能不是很高,但是安全性很高。`SipHash` 在中等大小的 `Key` 上,性能相当不错,但是对于小型的 `Key` (例如整数)或者大型 `Key` (例如字符串)来说,性能还是不够好。若你需要极致性能,例如实现算法,可以考虑这个库:[ahash](https://github.com/tkaitchuck/ahash)
最后,如果你想要了解 `HashMap` 更多的用法,请参见本书的标准库解析章节:[HashMap 常用方法](../../std/hashmap.md)
最后,如果你想要了解 `HashMap` 更多的用法,请参见本书的标准库解析章节:[HashMap 常用方法](https://course.rs/std/hashmap.html)
## 课后练习

@ -8,6 +8,6 @@
紧接着,第二个集合在全场的嘘声和羡慕眼光中闪亮登场,只见里面的元素排成一对一对的,彼此都手牵着手,非对方莫属,这种情深深雨蒙蒙的样子真是...挺欠扁的。 它就是 `HashMap` 类型,该类型允许你在里面存储 `KV` 对,每一个 `K` 都有唯一的 `V` 与之配对。
最后,请用热烈的掌声迎接我们的 `String` 集合,哦,抱歉,`String` 集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它。
最后,请用热烈的掌声迎接我们的 `String` 集合,哦,抱歉,`String` 集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](https://course.rs/basic/compound-type/string-slice.html)找它。
言归正传,本章所讲的 `Vector`、`HashMap` 再加上之前的 `String` 类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看 `Vector`

@ -37,7 +37,7 @@ v.push(1);
let v = vec![1, 2, 3];
```
同样,此处的 `v` 也无需标注类型,编译器只需检查它内部的元素即可自动推导出 `v` 的类型是 `Vec<i32>` Rust 中,整数默认类型是 `i32`,在[数值类型](../base-type/numbers.md#整数类型)中有详细介绍)。
同样,此处的 `v` 也无需标注类型,编译器只需检查它内部的元素即可自动推导出 `v` 的类型是 `Vec<i32>` Rust 中,整数默认类型是 `i32`,在[数值类型](https://course.rs/basic/base-type/numbers.html#整数类型)中有详细介绍)。
## 更新 Vector
@ -104,7 +104,7 @@ let does_not_exist = v.get(100);
##### 同时借用多个数组元素
既然涉及到借用数组元素,那么很可能会遇到同时借用多个数组元素的情况,还记得在[所有权和借用](../ownership/borrowing.md#借用规则总结)章节咱们讲过的借用规则嘛?如果记得,就来看看下面的代码:)
既然涉及到借用数组元素,那么很可能会遇到同时借用多个数组元素的情况,还记得在[所有权和借用](https://course.rs/basic/ownership/borrowing.html#借用规则总结)章节咱们讲过的借用规则嘛?如果记得,就来看看下面的代码:)
```rust
let mut v = vec![1, 2, 3, 4, 5];
@ -116,7 +116,7 @@ v.push(6);
println!("The first element is: {}", first);
```
先不运行,来推断下结果,首先 `first = &v[0]` 进行了不可变借用,`v.push` 进行了可变借用,如果 `first``v.push` 之后不再使用,那么该段代码可以成功编译(原因见[引用的作用域](../ownership/borrowing.md#可变引用与不可变引用不能同时存在))。
先不运行,来推断下结果,首先 `first = &v[0]` 进行了不可变借用,`v.push` 进行了可变借用,如果 `first``v.push` 之后不再使用,那么该段代码可以成功编译(原因见[引用的作用域](https://course.rs/basic/ownership/borrowing.html#可变引用与不可变引用不能同时存在))。
可是上面的代码中,`first` 这个不可变借用在可变借用 `v.push` 后被使用了,那么妥妥的,编译器就会报错:
@ -229,9 +229,9 @@ fn main() {
比枚举实现要稍微复杂一些,我们为 `V4``V6` 都实现了特征 `IpAddr`,然后将它俩的实例用 `Box::new` 包裹后,存在了数组 `v` 中,需要注意的是,这里必需手动的指定类型:`Vec<Box<dyn IpAddr>>`,表示数组 `v` 存储的是特征 `IpAddr` 的对象,这样就实现了在数组中存储不同的类型。
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象](../trait/trait-object.md)非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象](https://course.rs/basic/trait/trait-object.html)非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。
最后,如果你想要了解 `Vector` 更多的用法,请参见本书的标准库解析章节:[`Vector`常用方法](../../std/vector.md)
最后,如果你想要了解 `Vector` 更多的用法,请参见本书的标准库解析章节:[`Vector`常用方法](https://course.rs/std/vector.html)
## 课后练习

@ -387,7 +387,7 @@ pub struct BigY;
Created binary (application) `art` package
```
系统提示我们创建了一个二进制 `Package`,根据[之前章节](./crate-module/crate.md)学过的内容,可以知道该 `Package` 包含一个同名的二进制包:包名为 `art`,包根为 `src/main.rs`,该包可以编译成二进制然后运行。
系统提示我们创建了一个二进制 `Package`,根据[之前章节](https://course.rs/basic/crate-module/crate.html)学过的内容,可以知道该 `Package` 包含一个同名的二进制包:包名为 `art`,包根为 `src/main.rs`,该包可以编译成二进制然后运行。
现在,在 `src` 目录下创建一个 `lib.rs` 文件,同样,根据之前学习的知识,创建该文件等于又创建了一个库类型的包,包名也是 `art`,包根为 `src/lib.rs`,该包是是库类型的,因此往往作为依赖库被引入。

@ -22,7 +22,7 @@ fn main() {
}
```
数组语法跟 JavaScript 很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组 `array` 是存储在栈上**,性能也会非常优秀。与此对应,**动态数组 `Vector` 是存储在堆上**,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组 Vector](../collections/vector.md)。
数组语法跟 JavaScript 很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组 `array` 是存储在栈上**,性能也会非常优秀。与此对应,**动态数组 `Vector` 是存储在堆上**,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组 Vector](https://course.rs/basic/collections/vector.html)。
举个例子,在需要知道一年中各个月份名称的程序中,你很可能希望使用的是数组而不是动态数组。因为月份是固定的,它总是只包含 12 个元素:
@ -113,7 +113,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
## 数组切片
在之前的[章节](./string-slice.md#切片slice),我们有讲到 `切片` 这个概念,它允许你引用集合中的部分连续片段,而不是整个集合,对于数组也是,数组切片允许我们引用数组的一部分:
在之前的[章节](https://course.rs/basic/compound-type/string-slice.html#切片slice),我们有讲到 `切片` 这个概念,它允许你引用集合中的部分连续片段,而不是整个集合,对于数组也是,数组切片允许我们引用数组的一部分:
```rust
let a: [i32; 5] = [1, 2, 3, 4, 5];

@ -225,7 +225,7 @@ enum Option<T> {
其中 `T` 是泛型参数,`Some(T)`表示该枚举成员的数据类型是 `T`,换句话说,`Some` 可以包含任何类型的数据。
`Option<T>` 枚举是如此有用以至于它被包含在了 [`prelude`](../../appendix/prelude.md)prelude 属于 Rust 标准库Rust 会将最常用的类型、函数等提前引入其中,省得我们再手动引入)之中,你不需要将其显式引入作用域。另外,它的成员 `Some``None` 也是如此,无需使用 `Option::` 前缀就可直接使用 `Some``None`。总之,不能因为 `Some(T)``None` 中没有 `Option::` 的身影,就否认它们是 `Option` 下的卧龙凤雏。
`Option<T>` 枚举是如此有用以至于它被包含在了 [`prelude`](https://course.rs/appendix/prelude.html)prelude 属于 Rust 标准库Rust 会将最常用的类型、函数等提前引入其中,省得我们再手动引入)之中,你不需要将其显式引入作用域。另外,它的成员 `Some``None` 也是如此,无需使用 `Option::` 前缀就可直接使用 `Some``None`。总之,不能因为 `Some(T)``None` 中没有 `Option::` 的身影,就否认它们是 `Option` 下的卧龙凤雏。
再来看以下代码:
@ -271,7 +271,7 @@ not satisfied
总的来说,为了使用 `Option<T>` 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 `Some(T)` 值时运行,允许这些代码使用其中的 `T`。也希望一些代码在值为 `None` 时运行,这些代码并没有一个可用的 `T` 值。`match` 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
这里先简单看一下 `match` 的大致模样,在[模式匹配](../match-pattern/intro.md)中,我们会详细讲解:
这里先简单看一下 `match` 的大致模样,在[模式匹配](https://course.rs/basic/match-pattern/intro.html)中,我们会详细讲解:
```rust
fn plus_one(x: Option<i32>) -> Option<i32> {

@ -2,7 +2,7 @@
上一节中提到需要一个更高级的数据结构来帮助我们更好的抽象问题,结构体 `struct` 恰恰就是这样的复合数据结构,它是由其它数据类型组合而来。 其它语言也有类似的数据结构,不过可能有不同的名称,例如 `object``record` 等。
结构体跟之前讲过的[元组](./tuple.md)有些相像:都是由多种类型组合而成。但是与元组不同的是,结构体可以为内部的每个字段起一个富有含义的名称。因此结构体更加灵活更加强大,你无需依赖这些字段的顺序来访问和解析它们。
结构体跟之前讲过的[元组](https://course.rs/basic/compound-type/tuple.html)有些相像:都是由多种类型组合而成。但是与元组不同的是,结构体可以为内部的每个字段起一个富有含义的名称。因此结构体更加灵活更加强大,你无需依赖这些字段的顺序来访问和解析它们。
## 结构体语法
@ -124,7 +124,7 @@ fn build_user(email: String, username: String) -> User {
>
> 聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 `username` 发生了所有权转移?
>
> 仔细回想一下[所有权](../ownership/ownership.md#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 仔细回想一下[所有权](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>
> 值得注意的是:`username` 所有权被转移给了 `user2`,导致了 `user1` 无法再被使用,但是并不代表 `user1` 内部的其它字段不能被继续使用,例如:
@ -204,7 +204,7 @@ println!("{:?}", user1);
## 单元结构体(Unit-like Struct)
还记得之前讲过的基本没啥用的[单元类型](../base-type/char-bool.md#单元类型)吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。
还记得之前讲过的基本没啥用的[单元类型](https://course.rs/basic/base-type/char-bool.html#单元类型)吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。
如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 `单元结构体`
@ -223,7 +223,7 @@ impl SomeTrait for AlwaysEqual {
在之前的 `User` 结构体的定义中,有一处细节:我们使用了自身拥有所有权的 `String` 类型而不是基于引用的 `&str` 字符串切片类型。这是一个有意而为之的选择:因为我们想要这个结构体拥有它所有的数据,而不是从其它地方借用数据。
你也可以让 `User` 结构体从其它对象借用数据,不过这么做,就需要引入[生命周期(lifetimes)](../../advance/lifetime/basic.md)这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
你也可以让 `User` 结构体从其它对象借用数据,不过这么做,就需要引入[生命周期(lifetimes)](https://course.rs/advance/lifetime/basic.html)这个新概念(也是一个复杂的概念),简而言之,生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。
总之,如果你想在结构体中使用一个引用,就必须加上生命周期,否则就会报错:
@ -274,7 +274,7 @@ help: consider introducing a named lifetime parameter
|
```
未来在[生命周期](../../advance/lifetime/basic.md)中会讲到如何修复这个问题以便在结构体中存储引用,不过在那之前,我们会避免在结构体中使用引用类型。
未来在[生命周期](https://course.rs/advance/lifetime/basic.html)中会讲到如何修复这个问题以便在结构体中存储引用,不过在那之前,我们会避免在结构体中使用引用类型。
## 使用 `#[derive(Debug)]` 来打印结构体的信息

@ -80,9 +80,9 @@ fn main() {
}
```
上面代码中引入了 `std::convert::TryInto` 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中**,我们在上面用到了 `try_into` 方法,因此需要引入对应的特征。但是 Rust 又提供了一个非常便利的办法,把最常用的标准库中的特征通过[`std::prelude`](std::convert::TryInto)模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
上面代码中引入了 `std::convert::TryInto` 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中**,我们在上面用到了 `try_into` 方法,因此需要引入对应的特征。但是 Rust 又提供了一个非常便利的办法,把最常用的标准库中的特征通过[`std::prelude`](https://course.rs/appendix/prelude.html)模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
`try_into` 会尝试进行一次转换,并返回一个 `Result`,此时就可以对其进行相应的错误处理。由于我们的例子只是为了快速测试,因此使用了 `unwrap` 方法,该方法在发现错误时,会直接调用 `panic` 导致程序的崩溃退出,在实际项目中,请不要这么使用,具体见[panic](./exception-error.md#panic)部分。
`try_into` 会尝试进行一次转换,并返回一个 `Result`,此时就可以对其进行相应的错误处理。由于我们的例子只是为了快速测试,因此使用了 `unwrap` 方法,该方法在发现错误时,会直接调用 `panic` 导致程序的崩溃退出,在实际项目中,请不要这么使用,具体见[panic](https://course.rs/basic/result-error/panic.html#调用-panic)部分。
最主要的是 `try_into` 转换会捕获大类型向小类型转换时导致的溢出错误:
@ -275,7 +275,7 @@ impl<T> Clone for Container<T> {
- 这种转换永远都是未定义的
- 不,你不能这么做
- 不要多想,你没有那种幸运
4. 变形为一个未指定生命周期的引用会导致[无界生命周期](../advance/lifetime/advance.md)
4. 变形为一个未指定生命周期的引用会导致[无界生命周期](https://course.rs/advance/lifetime/advance.html)
5. 在复合类型之间互相变换时,你需要保证它们的排列布局是一模一样的!一旦不一样,那么字段就会得到不可预期的值,这也是未定义的行为,至于你会不会因此愤怒, **WHO CARES** ,你都用了变形了,老兄!
对于第 5 条,你该如何知道内存的排列布局是一样的呢?对于 `repr(C)` 类型和 `repr(transparent)` 类型来说,它们的布局是有着精确定义的。但是对于你自己的"普通却自信"的 Rust 类型 `repr(Rust)` 来说,它可不是有着精确定义的。甚至同一个泛型类型的不同实例都可以有不同的内存布局。 `Vec<i32>``Vec<u32>` 它们的字段可能有着相同的顺序,也可能没有。对于数据排列布局来说,**什么能保证,什么不能保证**目前还在 Rust 开发组的[工作任务](https://rust-lang.github.io/unsafe-code-guidelines/layout.html)中呢。

@ -38,7 +38,7 @@ mod front_of_house {
## 模块树
在[上一节](./crate.md)中,我们提到过 `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
crate

@ -6,7 +6,7 @@
## 基本引入方式
在 Rust 中,引入模块中的项有两种方式:[绝对路径和相对路径](./module.md#用路径引用模块),这两者在前面章节都有讲过,就不再赘述,先来看看使用绝对路径的引入方式。
在 Rust 中,引入模块中的项有两种方式:[绝对路径和相对路径](https://course.rs/basic/crate-module/module.html#用路径引用模块),这两者在前面章节都有讲过,就不再赘述,先来看看使用绝对路径的引入方式。
#### 绝对路径引入模块
@ -234,7 +234,7 @@ fn main() {
## 受限的可见性
在上一节中,我们学习了[可见性](./module.md#代码可见性)这个概念,这也是模块体系中最为核心的概念,控制了模块中哪些内容可以被外部看见,但是在实际使用时,光被外面看到还不行,我们还想控制哪些人能看,这就是 Rust 提供的受限可见性。
在上一节中,我们学习了[可见性](https://course.rs/basic/crate-module/module.html#代码可见性)这个概念,这也是模块体系中最为核心的概念,控制了模块中哪些内容可以被外部看见,但是在实际使用时,光被外面看到还不行,我们还想控制哪些人能看,这就是 Rust 提供的受限可见性。
例如,在 Rust 中,包是一个模块树,我们可以通过 `pub(crate) item;` 这种方式来实现:`item` 虽然是对外可见的,但是只在当前包内可见,外部包无法引用到该 `item`

@ -38,7 +38,7 @@ fn main() {
以上代码有以下几点要注意:
- **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5`
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](../appendix/expressions.md#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
```console
error[E0308]: if and else have incompatible types

@ -86,7 +86,7 @@ fn main() {
}
```
对于数值、字符串、数组,可以直接使用 `{:?}` 进行输出,但是对于结构体,需要[派生`Debug`](../appendix/derive.md)特征后,才能进行输出,总之很简单。
对于数值、字符串、数组,可以直接使用 `{:?}` 进行输出,但是对于结构体,需要[派生`Debug`](https://course.rs/appendix/derive.html)特征后,才能进行输出,总之很简单。
#### `Display` 特征
@ -168,7 +168,7 @@ fn main() {
#### 为外部类型实现 `Display` 特征
在 Rust 中,无法直接为外部类型实现外部特征,但是可以使用[`newtype`](./custom-type.md#newtype)解决此问题:
在 Rust 中,无法直接为外部类型实现外部特征,但是可以使用[`newtype`](https://course.rs/advance/into-types/custom-type.html#newtype)解决此问题:
```rust
struct Array(Vec<i32>);

@ -44,7 +44,7 @@ fn main() {
```
> 注意
> 在上面的 `add` 函数中,不要为 `i+j` 添加 `;`,这会改变语法导致函数返回 `()` 而不是 `i32`,具体参见[语句和表达式](./base-type/statement-expression.md)
> 在上面的 `add` 函数中,不要为 `i+j` 添加 `;`,这会改变语法导致函数返回 `()` 而不是 `i32`,具体参见[语句和表达式](https://course.rs/basic/base-type/statement-expression.html)
有几点可以留意下:

@ -251,7 +251,7 @@ impl Rectangle {
## 为枚举实现方法
枚举类型之所以强大,不仅仅在于它好用、可以[同一化类型](./compound-type/enum.md#同一化类型),还在于,我们可以像结构体一样,为枚举实现方法:
枚举类型之所以强大,不仅仅在于它好用、可以[同一化类型](https://course.rs/basic/compound-type/enum.html#同一化类型),还在于,我们可以像结构体一样,为枚举实现方法:
```rust
#![allow(unused)]

@ -100,7 +100,7 @@ let s = "hello"
#### 简单介绍 String 类型
之前提到过,本章会用 `String` 作为例子,因此这里会进行一下简单的介绍,具体的 `String` 学习请参见 [String 类型](../compound-type/string-slice.md)。
之前提到过,本章会用 `String` 作为例子,因此这里会进行一下简单的介绍,具体的 `String` 学习请参见 [String 类型](https://course.rs/basic/compound-type/string-slice.html)。
我们已经见过字符串字面值 `let s ="hello"``s` 是被硬编码进程序里的字符串值(类型为 `&str` )。字符串字面值是很方便的,但是它并不适用于所有场景。原因有二:

@ -95,7 +95,7 @@ note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose bac
其中,默认的方式就是 `栈展开`,这意味着 Rust 会回溯栈上数据和函数调用,因此也意味着更多的善后工作,好处是可以给出充分的报错信息和栈调用信息,便于事后的问题复盘。`直接终止`,顾名思义,不清理数据就直接退出程序,善后工作交与操作系统来负责。
对于绝大多数用户,使用默认选择是最好的,但是当你关心最终编译出的二进制可执行文件大小时,那么可以尝试去使用直接终止的方式,例如下面的配置修改 `Cargo.toml` 文件,实现在 [`release`](../first-try/cargo.md#手动编译和运行项目) 模式下遇到 `panic` 直接终止:
对于绝大多数用户,使用默认选择是最好的,但是当你关心最终编译出的二进制可执行文件大小时,那么可以尝试去使用直接终止的方式,例如下面的配置修改 `Cargo.toml` 文件,实现在 [`release`](https://course.rs/first-try/cargo.html#手动编译和运行项目) 模式下遇到 `panic` 直接终止:
```rust
[profile.release]

@ -30,7 +30,7 @@ fn main() {
> 有几种常用的方式,此处更推荐第二种方法:
>
> - 第一种是查询标准库或者三方库文档,搜索 `File`,然后找到它的 `open` 方法
> - 在 [Rust IDE](../../first-try/editor.md) 章节,我们推荐了 `VSCode` IDE 和 `rust-analyzer` 插件,如果你成功安装的话,那么就可以在 `VSCode` 中很方便的通过代码跳转的方式查看代码,同时 `rust-analyzer` 插件还会对代码中的类型进行标注,非常方便好用!
> - 在 [Rust IDE](https://course.rs/first-try/editor.html) 章节,我们推荐了 `VSCode` IDE 和 `rust-analyzer` 插件,如果你成功安装的话,那么就可以在 `VSCode` 中很方便的通过代码跳转的方式查看代码,同时 `rust-analyzer` 插件还会对代码中的类型进行标注,非常方便好用!
> - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你:
```rust
@ -53,7 +53,7 @@ error[E0308]: mismatched types
上面代码,故意将 `f` 类型标记成整形,编译器立刻不乐意了,你是在忽悠我吗?打开文件操作返回一个整形?来,大哥来告诉你返回什么:`std::result::Result<std::fs::File, std::io::Error>`,我的天呐,怎么这么长的类型!
别慌,其实很简单,首先 `Result` 本身是定义在 `std::result` 中的,但是因为 `Result` 很常用,所以就被包含在了 [`prelude`](../../appendix/prelude.md) 中(将常用的东东提前引入到当前作用域内),因此无需手动引入 `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` 枚举提供:
@ -105,7 +105,7 @@ fn main() {
- 如果是文件不存在错误 `ErrorKind::NotFound`,就创建文件,这里创建文件`File::create` 也是返回 `Result`,因此继续用 `match` 对其结果进行处理:创建成功,将新的文件句柄赋值给 `f`,如果失败,则 `panic`
- 剩下的错误,一律 `panic`
虽然很清晰,但是代码还是有些啰嗦,我们会在[简化错误处理](../../advance/errors.md)一章重点讲述如何写出更优雅的错误。
虽然很清晰,但是代码还是有些啰嗦,我们会在[简化错误处理](https://course.rs/advance/errors.html)一章重点讲述如何写出更优雅的错误。
## 失败就 panic: unwrap 和 expect

@ -232,7 +232,7 @@ Up!
因为 `fly` 方法的参数是 `self`,当显式调用时,编译器就可以根据调用的类型( `self` 的类型)决定具体调用哪个方法。
这个时候问题又来了,如果方法没有 `self` 参数呢?稍等,估计有读者会问:还有方法没有 `self` 参数?看到这个疑问,作者的眼泪不禁流了下来,大明湖畔的[关联函数](../method.md#关联函数),你还记得嘛?
这个时候问题又来了,如果方法没有 `self` 参数呢?稍等,估计有读者会问:还有方法没有 `self` 参数?看到这个疑问,作者的眼泪不禁流了下来,大明湖畔的[关联函数](https://course.rs/basic/method.html#关联函数),你还记得嘛?
但是成年人的世界,就算再伤心,事还得做,咱们继续:

@ -55,7 +55,7 @@ fn main() {
fn largest<T>(list: &[T]) -> T {
```
该泛型函数的作用是从列表中找出最大的值,其中列表中的元素类型为 T。首先 `largest<T>` 对泛型参数 `T` 进行了声明,然后才在函数参数中进行使用该泛型参数 `list: &[T]` (还记得 `&[T]` 类型吧?这是[数组切片](../compound-type/array#数组切片))。
该泛型函数的作用是从列表中找出最大的值,其中列表中的元素类型为 T。首先 `largest<T>` 对泛型参数 `T` 进行了声明,然后才在函数参数中进行使用该泛型参数 `list: &[T]` (还记得 `&[T]` 类型吧?这是[数组切片](https://course.rs/basic/compound-type/array.html#数组切片))。
总之,我们可以这样理解这个函数定义:函数 `largest` 有泛型类型 `T`,它有个参数 `list`,其类型是元素为 `T` 的数组切片,最后,该函数返回值的类型也是 `T`
@ -294,7 +294,7 @@ impl Point<f32> {
在之前的泛型中,可以抽象为一句话:针对类型实现的泛型,所有的泛型都是为了抽象不同的类型,那有没有针对值的泛型?可能很多同学感觉很难理解,值怎么使用泛型?不急,我们先从数组讲起。
在[数组](../compound-type/array.md)那节,有提到过很重要的一点:`[i32; 2]` 和 `[i32; 3]` 是不同的数组类型,比如下面的代码:
在[数组](https://course.rs/basic/compound-type/array.html)那节,有提到过很重要的一点:`[i32; 2]` 和 `[i32; 3]` 是不同的数组类型,比如下面的代码:
```rust
fn display_array(arr: [i32; 3]) {

@ -339,11 +339,11 @@ fn returns_summarizable(switch: bool) -> impl Summary {
expected struct `Post`, found struct `Weibo`
```
报错提示我们 `if``else` 返回了不同的类型。如果想要实现返回不同的类型,需要使用下一章节中的[特征对象](./trait-object.md)。
报错提示我们 `if``else` 返回了不同的类型。如果想要实现返回不同的类型,需要使用下一章节中的[特征对象](https://course.rs/basic/trait/trait-object.html)。
## 修复上一节中的 `largest` 函数
还记得上一节中的[例子](./generic#泛型详解)吧,当时留下一个疑问,该如何解决编译报错:
还记得上一节中的[例子](https://course.rs/basic/trait/generic.html#泛型详解)吧,当时留下一个疑问,该如何解决编译报错:
```rust
error[E0369]: binary operation `>` cannot be applied to type `T` // 无法在 `T` 类型上应用`>`运算符
@ -390,7 +390,7 @@ error[E0507]: cannot move out of borrowed content
| cannot move out of borrowed content
```
错误的核心是 `cannot move out of type [T], a non-copy slice`,原因是 `T` 没有[实现 `Copy` 特性](../ownership/ownership.md#拷贝浅拷贝),因此我们只能把所有权进行转移,毕竟只有 `i32` 等基础类型才实现了 `Copy` 特性,可以存储在栈上,而 `T` 可以指代任何类型(严格来说是实现了 `PartialOrd` 特征的所有类型)。
错误的核心是 `cannot move out of type [T], a non-copy slice`,原因是 `T` 没有[实现 `Copy` 特性](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝),因此我们只能把所有权进行转移,毕竟只有 `i32` 等基础类型才实现了 `Copy` 特性,可以存储在栈上,而 `T` 可以指代任何类型(严格来说是实现了 `PartialOrd` 特征的所有类型)。
因此,为了让 `T` 拥有 `Copy` 特性,我们可以增加特征约束:
@ -420,7 +420,7 @@ fn main() {
}
```
如果并不希望限制 `largest` 函数只能用于实现了 `Copy` 特征的类型,我们可以在 `T` 的特征约束中指定 [`Clone` 特征](../ownership/ownership.md#克隆深拷贝) 而不是 `Copy` 特征。并克隆 `list` 中的每一个值使得 `largest` 函数拥有其所有权。使用 `clone` 函数意味着对于类似 `String` 这样拥有堆上数据的类型,会潜在地分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
如果并不希望限制 `largest` 函数只能用于实现了 `Copy` 特征的类型,我们可以在 `T` 的特征约束中指定 [`Clone` 特征](https://course.rs/basic/ownership/ownership.html#克隆深拷贝) 而不是 `Copy` 特征。并克隆 `list` 中的每一个值使得 `largest` 函数拥有其所有权。使用 `clone` 函数意味着对于类似 `String` 这样拥有堆上数据的类型,会潜在地分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。
另一种 `largest` 的实现方式是返回在 `list``T` 值的引用。如果我们将函数返回值从 `T` 改为 `&T` 并改变函数体使其能够返回一个引用,我们将不需要任何 `Clone``Copy` 的特征约束而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!
@ -434,7 +434,7 @@ fn main() {
总之,`derive` 派生出来的是 Rust 默认给我们提供的特征,在开发过程中极大的简化了自己手动实现相应特征的需求,当然,如果你有特殊的需求,还可以自己手动重载该实现。
详细的 `derive` 列表参见[附录-派生特征](../../appendix/derive.md)。
详细的 `derive` 列表参见[附录-派生特征](https://course.rs/appendix/derive.html)。
## 调用方法需要引入特征
@ -458,13 +458,13 @@ fn main() {
上面代码中引入了 `std::convert::TryInto` 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中**,我们在上面用到了 `try_into` 方法,因此需要引入对应的特征。
但是 Rust 又提供了一个非常便利的办法,即把最常用的标准库中的特征通过 [`std::prelude`](std::convert::TryInto) 模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
但是 Rust 又提供了一个非常便利的办法,即把最常用的标准库中的特征通过 [`std::prelude`](https://course.rs/appendix/prelude.html) 模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
## 几个综合例子
#### 为自定义类型实现 `+` 操作
在 Rust 中除了数值类型的加法,`String` 也可以做[加法](../compound-type/string-slice.md#操作字符串),因为 Rust 为该类型实现了 `std::ops::Add` 特征,同理,如果我们为自定义类型实现了该特征,那就可以自己实现 `Point1 + Point2` 的操作:
在 Rust 中除了数值类型的加法,`String` 也可以做[加法](https://course.rs/basic/compound-type/string-slice.html#操作字符串),因为 Rust 为该类型实现了 `std::ops::Add` 特征,同理,如果我们为自定义类型实现了该特征,那就可以自己实现 `Point1 + Point2` 的操作:
```rust
use std::ops::Add;

@ -14,7 +14,7 @@
在命名方面,和其它语言没有区别,不过当给变量命名时,需要遵循 [Rust 命名规范](https://course.rs/practice/naming.html)。
> Rust 语言有一些**关键字**_keywords_和其他语言一样这些关键字都是被保留给 Rust 语言使用的,因此,它们不能被用作变量或函数的名称。在 [附录 A](../appendix/keywords) 中可找到关键字列表。
> Rust 语言有一些**关键字**_keywords_和其他语言一样这些关键字都是被保留给 Rust 语言使用的,因此,它们不能被用作变量或函数的名称。在 [附录 A](https://course.rs/appendix/keywords.html) 中可找到关键字列表。
## 变量绑定
@ -145,7 +145,7 @@ fn main() {
- 常量不允许使用 `mut`。**常量不仅仅默认不可变,而且自始至终不可变**,因为常量在编译完成后,已经确定它的值。
- 常量使用 `const` 关键字而不是 `let` 关键字来声明,并且值的类型**必须**标注。
我们将在下一节[数据类型](./base-type/index.md)中介绍,因此现在暂时无需关心细节。
我们将在下一节[数据类型](https://course.rs/basic/base-type/index.html)中介绍,因此现在暂时无需关心细节。
下面是一个常量声明的例子,其常量名为 `MAX_POINTS`,值设置为 `100,000`Rust 常量的命名约定是全部字母都使用大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性):
@ -222,7 +222,7 @@ error[E0308]: mismatched types
error: aborting due to previous error
```
显然Rust 对类型的要求很严格,不允许将整数类型 `usize` 赋值给字符串类型。`usize` 是一种 CPU 相关的整数类型,在[数值类型](./base-type/numbers#整数类型)中有详细介绍。
显然Rust 对类型的要求很严格,不允许将整数类型 `usize` 赋值给字符串类型。`usize` 是一种 CPU 相关的整数类型,在[数值类型](https://course.rs/basic/base-type/numbers.html#整数类型)中有详细介绍。
万事开头难,到目前为止,都进展很顺利,那下面开始,咱们正式进入 Rust 的类型世界,看看有哪些挑战在前面等着大家。

@ -114,7 +114,7 @@ $ cargo check
Finished dev [unoptimized + debuginfo] target(s) in 0.06s
```
> Rust 虽然编译速度还行,但是还是不能 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析,甚至连如何优化编译速度都成了一门学问: [优化编译速度](../profiling/compiler/speed-up.md)
> Rust 虽然编译速度还行,但是还是不能 Go 语言相提并论,因为 Rust 需要做很多复杂的编译优化和语言特性解析,甚至连如何优化编译速度都成了一门学问: [优化编译速度](https://course.rs/profiling/compiler/speed-up.html)
## Cargo.toml 和 Cargo.lock

@ -4,9 +4,9 @@
## 多国语言的"世界,你好"
还记得大明湖畔等你的 [VSCode IDE](./editor.md) 和通过 `Cargo` 创建的 [世界,你好](./cargo.md) 工程吗?
还记得大明湖畔等你的 [VSCode IDE](https://course.rs/first-try/editor.md) 和通过 `Cargo` 创建的 [世界,你好](https://course.rs/first-try/cargo.html) 工程吗?
现在使用 VSCode 打开 [上一节](./cargo.md) 中创建的 `world_hello` 工程,然后进入 `main.rs` 文件。(此文件是当前 Rust 工程的入口文件,和其它语言几无区别。)
现在使用 VSCode 打开 [上一节](https://course.rs/first-try/cargo.html) 中创建的 `world_hello` 工程,然后进入 `main.rs` 文件。(此文件是当前 Rust 工程的入口文件,和其它语言几无区别。)
接下来,对世界友人给予热切的问候:
@ -111,11 +111,11 @@ fn main() {
- 高阶函数编程:函数可以作为参数也能作为返回值,例如 `.map(|field| field.trim())`,这里 `map` 方法中使用闭包函数作为参数,也可以称呼为 `匿名函数`、`lambda 函数`。
- 类型标注:`if let Ok(length) = fields[1].parse::<f32>()`,通过 `::<f32>` 的使用,告诉编译器 `length` 是一个 `f32` 类型的浮点数。这种类型标注不是很常用,但是在编译器无法推断出你的数据类型时,就很有用了。
- 条件编译:`if cfg!(debug_assertions)`,说明紧跟其后的输出(打印)只在 `debug` 模式下生效。
- 隐式返回Rust 提供了 `return` 关键字用于函数返回,但是在很多时候,我们可以省略它。因为 Rust 是 [**基于表达式的语言**](../basic/base-type/statement-expression.md)。
- 隐式返回Rust 提供了 `return` 关键字用于函数返回,但是在很多时候,我们可以省略它。因为 Rust 是 [**基于表达式的语言**](https://course.rs/basic/base-type/statement-expression.html)。
在终端中运行上述代码时,会看到很多 `debug: ...` 的输出,上面有讲,这些都是 `条件编译` 的输出,那么该怎么消除掉这些输出呢?
读者大大普遍冰雪聪明,肯定已经想到:是的,在 [认识 Cargo](./cargo.md#手动编译和运行项目)中,曾经介绍过 `--release` 参数,因为 `cargo run` 默认是运行 `debug` 模式。因此想要消灭那些 `debug:` 输出,需要更改为其它模式,其中最常用的模式就是 `--release` 也就是生产发布的模式。
读者大大普遍冰雪聪明,肯定已经想到:是的,在 [认识 Cargo](https://course.rs/first-try/cargo.html#手动编译和运行项目)中,曾经介绍过 `--release` 参数,因为 `cargo run` 默认是运行 `debug` 模式。因此想要消灭那些 `debug:` 输出,需要更改为其它模式,其中最常用的模式就是 `--release` 也就是生产发布的模式。
具体运行代码就不给了,留给大家作为一个小练习,建议亲自动手尝试下。

@ -161,4 +161,4 @@ cargo 1.57.0 (b2e52d7ca 2021-10-21)
安装 Rust 的同时也会在本地安装一个文档服务,方便我们离线阅读:运行 `rustup doc` 让浏览器打开本地文档。
每当遇到标准库提供的类型或函数不知道怎么用时,都可以在 API 文档中查找到!具体参见 [在标准库寻找你想要的内容](../std/search.md)。
每当遇到标准库提供的类型或函数不知道怎么用时,都可以在 API 文档中查找到!具体参见 [在标准库寻找你想要的内容](https://course.rs/std/search.html)。

@ -69,7 +69,7 @@ error[E0310]: the parameter type `impl Fn(&str) -> Res` may not live long enough
callback: Option<Box<dyn Fn(&str) -> Res>>,
```
众所周知,闭包跟哈姆雷特一样,每一个都有[自己的类型](../../advance/functional-programing/closure.md#闭包作为函数返回值),因此我们无法通过类型标注的方式来声明一个闭包,那么只有一个办法,就是使用特征对象,因此上面代码中,通过`Box<dyn Trait>`的方式把闭包特征封装成一个特征对象。
众所周知,闭包跟哈姆雷特一样,每一个都有[自己的类型](https://course.rs/advance/functional-programing/closure.html#闭包作为函数返回值),因此我们无法通过类型标注的方式来声明一个闭包,那么只有一个办法,就是使用特征对象,因此上面代码中,通过`Box<dyn Trait>`的方式把闭包特征封装成一个特征对象。
## 深入挖掘报错原因
@ -89,7 +89,7 @@ struct Foo<'a> {
};
```
除非`x`字段借用了`'static`的引用,否则`'a`肯定比`'static`要小,那么该结构体实例的生命周期肯定不是`'static`: `'a: 'static`的限制不会被满足([HRTB](../../advance/lifetime/advance.md#生命周期约束HRTB))。
除非`x`字段借用了`'static`的引用,否则`'a`肯定比`'static`要小,那么该结构体实例的生命周期肯定不是`'static`: `'a: 'static`的限制不会被满足([HRTB](https://course.rs/advance/lifetime/advance.html#生命周期约束HRTB))。
对于特征对象来说,它没有包含非`'static`的引用,因此它隐式的具有`'static`生命周期, `Box<dyn Trait>`就跟`Box<dyn Trait + 'static>`是等价的。

@ -168,7 +168,7 @@ impl A {
我们来逐步深入分析下:
1. 首先为`two`方法增加一下生命周期标识: `fn two<'a>(&'a mut self) -> &'a i32 { .. }`, 这里根据生命周期的[消除规则](../../advance/lifetime/basic.md#三条消除规则)添加的
1. 首先为`two`方法增加一下生命周期标识: `fn two<'a>(&'a mut self) -> &'a i32 { .. }`, 这里根据生命周期的[消除规则](https://course.rs/advance/lifetime/basic.html#三条消除规则)添加的
2. 根据生命周期标识可知:`two`中返回的`k`的生命周期必须是`'a`
3. 根据第 2 条,又可知:`let k = self.one();`中对`self`的借用生命周期也是`'a`
4. 因为`k`的借用发生在`loop`循环内,因此它需要小于等于循环的生命周期,但是根据之前的推断,它又要大于等于函数的生命周期`'a`,而函数的生命周期又大于等于循环生命周期,

@ -39,7 +39,7 @@ fn count(self) -> usize
## 迭代器回顾
在[迭代器](../advance/functional-programing/iterator.md#消费者与适配器)章节中,我们曾经学习过两个概念:迭代器适配器和消费者适配器,前者用于对迭代器中的元素进行操作,最终生成一个新的迭代器,例如`map`、`filter`等方法;而后者用于消费掉迭代器,最终产生一个结果,例如`collect`方法, 一个典型的示例如下:
在[迭代器](https://course.rs/advance/functional-programing/iterator.html#消费者与适配器)章节中,我们曾经学习过两个概念:迭代器适配器和消费者适配器,前者用于对迭代器中的元素进行操作,最终生成一个新的迭代器,例如`map`、`filter`等方法;而后者用于消费掉迭代器,最终产生一个结果,例如`collect`方法, 一个典型的示例如下:
```rust
let v1: Vec<i32> = vec![1, 2, 3];

@ -53,7 +53,7 @@ println!("{:?}",resolvers);
## 回顾下迭代器
在迭代器章节中,我们曾经提到过,迭代器的[适配器](../advance/functional-programing/iterator.md#消费者与适配器)分为两种:消费者适配器和迭代器适配器,前者用来将一个迭代器变为指定的集合类型,往往通过`collect`实现;后者用于生成一个新的迭代器,例如上例中的`map`。
在迭代器章节中,我们曾经提到过,迭代器的[适配器](https://course.rs/advance/functional-programing/iterator.html#消费者与适配器)分为两种:消费者适配器和迭代器适配器,前者用来将一个迭代器变为指定的集合类型,往往通过`collect`实现;后者用于生成一个新的迭代器,例如上例中的`map`。
还提到过非常重要的一点: **迭代器适配器都是懒惰的,只有配合消费者适配器使用时,才会进行求值**.

@ -46,7 +46,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
因为涉及到相等比较( `==` )和错误信息打印,因此两个表达式的值必须实现 `PartialEq``Debug` 特征,其中所有的原生类型和大多数标准库类型都实现了这些特征,而对于你自己定义的结构体、枚举,如果想要对其进行 `assert_eq!` 断言,则需要实现 `PartialEq``Debug` 特征:
- 若希望实现个性化相等比较和错误打印,则需手动实现
- 否则可以为自定义的结构体、枚举添加 `#[derive(PartialEq, Debug)]` 注解,来[自动派生](../appendix/derive.md)对应的特征
- 否则可以为自定义的结构体、枚举添加 `#[derive(PartialEq, Debug)]` 注解,来[自动派生](https://course.rs/appendix/derive.html)对应的特征
**以上特征限制对于下面即将讲解的 `assert_ne!` 一样有效,** 就不再重复讲述。

@ -32,7 +32,7 @@
- [`autobenches`](cargo-target.md#对象自动发现) — 禁止 bench 文件的自动发现
- [`resolver`](resolver.md#resolver-versions) — 设置依赖解析器( dependency resolver)
- Cargo Target 列表: (查看 [Target 配置](cargo-target.md#Target配置) 获取详细设置)
- [`[lib]`](./cargo-target.md#库对象library) — Library target 设置.
- [`[lib]`](cargo-target.md#库对象library) — Library target 设置.
- [`[[bin]]`](cargo-target.md#二进制对象binaries) — Binary target 设置.
- [`[[example]]`](cargo-target.md#示例对象examples) — Example target 设置.
- [`[[test]]`](cargo-target.md#测试对象tests) — Test target 设置.

Loading…
Cancel
Save