Merge pull request #1 from sunface/main

sync
pull/547/head
Rustln 3 years ago committed by GitHub
commit b8ef060798
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -42,8 +42,6 @@
- [@JesseAtSZ](https://github.com/JesseAtSZ)
- [@1132719438](https://github.com/1132719438)
- [@Mintnoii](https://github.com/Mintnoii)
- [@mg-chao](https://github.com/mg-chao)
- [@codemystery](https://github.com/codemystery)
尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~

@ -59,7 +59,6 @@
- [注释和文档](basic/comment.md)
- [格式化输出](basic/formatted-output.md)
- [Rust 高级进阶](advance/intro.md)
- [生命周期](advance/lifetime/intro.md)
- [认识生命周期](advance/lifetime/basic.md)
- [深入生命周期](advance/lifetime/advance.md)
@ -91,7 +90,6 @@
- [实践应用:多线程 Web 服务器 todo](advance/concurrency-with-threads/web-server.md)
- [全局变量](advance/global-variable.md)
- [错误处理](advance/errors.md)
- [Unsafe Rust](advance/unsafe/intro.md)
- [五种兵器](advance/unsafe/superpowers.md)
- [内联汇编 todo](advance/unsafe/inline-asm.md)
@ -102,7 +100,6 @@
## 专题内容,每个专题都配套一个小型项目进行实践
- [自动化测试](test/intro.md)
- [编写测试及控制执行](test/write-tests.md)
- [单元测试和集成测试](test/unit-integration-test.md)
- [断言 assertion](test/assertion.md)
@ -118,7 +115,6 @@
- [一些疑难问题的解决办法](async/pain-points-and-workarounds.md)
- [实践应用Async Web 服务器](async/web-server.md)
- [Tokio 使用指南](tokio/intro.md)
- [tokio 概览](tokio/overview.md)
- [使用初印象](tokio/getting-startted.md)
- [创建异步任务](tokio/spawning.md)
@ -133,7 +129,6 @@
- [异步跟同步共存](tokio/bridging-with-sync.md)
- [Cargo 使用指南](cargo/intro.md)
- [上手使用](cargo/getting-started.md)
- [基础指南](cargo/guide/intro.md)
- [为何会有 Cargo](cargo/guide/why-exist.md)
@ -158,17 +153,19 @@
- [构建脚本 build.rs](cargo/reference/build-script/intro.md)
- [构建脚本示例](cargo/reference/build-script/examples.md)
- [手把手带你实现链表 doing](linked-list/intro.md)
- [易混淆概念解析](confonding/intro.md)
- [手把手带你实现链表 doing](too-many-lists/intro.md)
- [我们到底需不需要链表](too-many-lists/do-we-need-it.md)
- [易混淆概念解析](confonding/intro.md)
- [切片和切片引用](confonding/slice.md)
- [String、&str 和 str](confonding/string.md)
- [Eq 和 PartialEq](confonding/eq.md)
- [String、&str 和 str todo](confonding/string.md)
- [原生指针、引用和智能指针 todo](confonding/pointer.md)
- [作用域、生命周期和 NLL todo](confonding/lifetime.md)
- [move、Copy 和 Clone todo](confonding/move-copy.md)
- [对抗编译检查 doing](fight-with-compiler/intro.md)
- [幽灵数据(todo)](fight-with-compiler/phantom-data.md)
- [生命周期](fight-with-compiler/lifetime/intro.md)
- [生命周期过大-01](fight-with-compiler/lifetime/too-long1.md)
@ -194,7 +191,6 @@
- [线程间传递消息导致主线程无法结束](pitfalls/main-with-channel-blocked.md)
- [Rust 最佳实践 doing](practice/intro.md)
- [日常开发三方库精选](practice/third-party-libs.md)
- [命名规范](practice/naming.md)
- [代码开发实践 todo](practice/best-pratice.md)
@ -229,7 +225,6 @@
- [Option 枚举 todo](profiling/compiler/optimization/option.md)
- [标准库解析 todo](std/intro.md)
- [标准库使用最佳时间 todo](std/search.md)
- [Vector 常用方法 todo](std/vector.md)
- [HashMap todo](std/hashmap.md)

@ -225,9 +225,9 @@ fn main() {
println!("Gadget {} owned by {}", gadget.id, gadget.owner.name);
}
// 在 main 函数的最后gadget_ownergadget1 和 daget2 都被销毁。
// 在 main 函数的最后gadget_ownergadget1 和 gadget2 都被销毁。
// 具体是,因为这几个结构体之间没有了强引用(`Rc<T>`),所以,当他们销毁的时候。
// 首先 gadget1 和 gadget2 被销毁。
// 首先 gadget2 和 gadget1 被销毁。
// 然后因为 gadget_owner 的引用数量为 0所以这个对象可以被销毁了。
// 循环引用问题也就避免了
}

@ -355,7 +355,7 @@ impl Counter {
}
```
我们为计数器 `Counter` 实现了一个关联函数 `new`,用于创建新的计数器实例。下面们继续为计数器实现 `Iterator` 特征:
我们为计数器 `Counter` 实现了一个关联函数 `new`,用于创建新的计数器实例。下面们继续为计数器实现 `Iterator` 特征:
```rust
impl Iterator for Counter {

@ -1,16 +1,39 @@
# Deref 解引用
在开始之前,我们先来看一段代码:
```rust
#[derive(Debug)]
struct Person {
name: String,
age: u8
}
何为智能指针?能不让你写出 ******s 形式的解引用,我认为就是智能: ),智能指针的名称来源,主要就在于它实现了 `Deref``Drop` 特征,这两个特征可以智能地帮助我们节省使用上的负担:
impl Person {
fn new(name: String, age: u8) -> Self {
Person { name, age}
}
fn display(self: &mut Person, age: u8) {
let Person{name, age} = &self;
}
}
```
以上代码有一个很奇怪的地方:在 `display` 方法中,`self` 是 `&mut Person` 的类型,接着我们对其取了一次引用 `&self`,此时 `&self` 的类型是 `&&mut Person`,然后我们又将其和 `Person` 类型进行匹配,取出其中的值。
那么问题来了Rust 不是号称安全的语言吗?为何允许将 `&&mut Person``Person` 进行匹配呢?答案就在本章节中,等大家学完后,再回头自己来解决这个问题 :) 下面正式开始咱们的新章节学习。
何为智能指针?能不让你写出 `****s` 形式的解引用,我认为就是智能: ),智能指针的名称来源,主要就在于它实现了 `Deref``Drop` 特征,这两个特征可以智能地帮助我们节省使用上的负担:
- `Deref` 可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 `*T`
- `Drop` 允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
下面先来看看 `Deref` 特征是如何工作的。
先来看看 `Deref` 特征是如何工作的。
## 通过 `*` 获取引用背后的值
在正式讲解 `Deref` 之前,我们先来看下常规引用的解引用。
常规引用是一个指针类型,包含了目标数据存储的内存地址。对常规引用使用 `*` 操作符,就可以通过解引用的方式获取到内存地址对应的数据值:
```rust

@ -300,3 +300,8 @@ 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)
## 课后练习
> [Rust By Practice](https://zh.practice.rs/collections/hashmap.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -232,3 +232,8 @@ fn main() {
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象](../trait/trait-object.md)非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。
最后,如果你想要了解 `Vector` 更多的用法,请参见本书的标准库解析章节:[`Vector`常用方法](../../std/vector.md)
## 课后练习
> [Rust By Practice](https://zh.practice.rs/collections/vector.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -430,3 +430,4 @@ for b in "中国人".bytes() {
> Rust By Practice支持代码在线编辑和运行并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。
> - [字符串](https://zh.practice.rs/compound-types/string.html)
> - [切片](https://zh.practice.rs/compound-types/slice.html)
> - [String](https://zh.practice.rs/collections/String.html)

@ -28,7 +28,7 @@ fn main() {
- `match` 的匹配必须要穷举出所有可能,因此这里用 `_` 来代表未列出的所有可能性
- `match` 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
- **X | Y**逻辑运算符 `或`,代表该分支可以匹配 `X` 也可以匹配 `Y`,只要满足一个即可
- **X | Y**类似逻辑运算符 `或`,代表该分支可以匹配 `X` 也可以匹配 `Y`,只要满足一个即可
其实 `match` 跟其他语言中的 `switch` 非常像,`_` 类似于 `switch` 中的 `default`

@ -418,3 +418,8 @@ fn main() {
当然,解决办法还是有的,要不怎么说 Rust 是极其强大灵活的编程语言Rust 提供了一个特征叫 [`Deref`](https://course.rs/advance/smart-pointer/deref.html),实现该特征后,可以自动做一层类似类型转换的操作,可以将 `Wrapper` 变成 `Vec<String>` 来使用。这样就会像直接使用数组那样去使用 `Wrapper`,而无需为每一个操作都添加上 `self.0`
同时,如果不想 `Wrapper` 暴漏底层数组的所有方法,我们还可以为 `Wrapper` 去重载这些方法,实现隐藏的目的。
## 课后练习
> [Rust By Practice](https://zh.practice.rs/generics-traits/advanced-traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -249,13 +249,13 @@ fn main() {
因为 `String` 类型没有实现 `Draw` 特征,编译器直接就会报错,不会让上述代码运行。如果想要 `String` 类型被渲染在屏幕上,那么只需要为其实现 `Draw` 特征即可,非常容易。
#### &dyn 和 Box\<dyn\>的区别
<!-- #### &dyn 和 Box\<dyn\>的区别
前文提到, `&dyn``Box<dyn>` 都可以用于特征对象,因此在功能上 `&dyn``Box<dyn>` 几乎没有区别,唯一的区别就是:`&dyn` 减少了一次指针调用。
因为 `Box<dyn>` 是一个宽指针(`fat pointer`),它需要一次额外的解引用后,才能获取到指向 `vtable` 的指针,然后再通过该指针访问 `vtable` 查询到具体的函数指针,最后进行调用。
所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑 `&dyn`
所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑 `&dyn` -->
注意 `dyn` 不能单独作为特征对象的定义,例如下面的代码编译器会报错,原因是特征对象可以是任意实现了某个特征的类型,编译器在编译期不知道该类型的大小,不同的类型大小是不同的。
@ -357,3 +357,8 @@ error[E0038]: the trait `std::clone::Clone` cannot be made into an object
```
这意味着不能以这种方式使用此特征作为特征对象。
## 课后练习
> [Rust By Practice](https://zh.practice.rs/generics-traits/trait-object.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -576,3 +576,8 @@ fn main() {
以上两个例子较为复杂,目的是为读者展示下真实的使用场景长什么样,因此需要读者细细阅读,最终消化这些知识对于你的 Rust 之路会有莫大的帮助。
最后,特征和特征约束,是 Rust 中极其重要的概念,如果你还是没搞懂,强烈建议回头再看一遍,或者寻找相关的资料进行补充学习。如果已经觉得掌握了,那么就可以进入下一节的学习。
## 课后练习
> [Rust By Practice](https://zh.practice.rs/generics-traits/traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -4,7 +4,7 @@
## 为何要手动设置变量的可变性?
在其它大多数语言中,变量一旦创建要么是可变的要么是不可变的ClojureScript,前者为编程提供了灵活性,后者为编程提供了安全性,而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
在其它大多数语言中,要么只支持声明可变的变量,要么只支持声明不可变的变量( 例如函数式语言 ),前者为编程提供了灵活性,后者为编程提供了安全性,而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
能想要学习 Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**,那么两者都要的权衡是什么呢?这就是 Rust 开发团队为我们做出的贡献,两者都要意味着 Rust 语言底层代码的实现复杂度大幅提升,因此 Salute to The Rust Team!
@ -136,6 +136,8 @@ fn main() {
需要注意的是,使用 `+=` 的赋值语句还不支持解构式赋值。
> 这里用到了模式匹配的一些语法,如果大家看不懂没关系,可以在学完模式匹配章节后,再回头来看。
### 变量和常量之间的差异
变量的值不能更改可能让你想起其他另一个很多语言都有的编程概念:**常量**(_constant_)。与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异:

@ -0,0 +1,127 @@
# Eq 和 PartialEq
在 Rust 中,想要重载操作符,你就需要实现对应的特征。
例如 `<`、`<=`、`>` 和 `>=` 需要实现 `PartialOrd` 特征:
```rust
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
```
再比如, `+` 号需要实现 `std::ops::Add` 特征,而本文的主角 `Eq``PartialEq` 正是 `==``!=` 所需的特征,那么问题来了,这两个特征有何区别?
我相信很多同学都说不太清楚,包括一些老司机,而且就算是翻文档,可能也找不到特别明确的解释。如果大家看过标准库示例,可能会看过这个例子:
```rust
enum BookFormat { Paperback, Hardback, Ebook }
struct Book {
isbn: i32,
format: BookFormat,
}
impl PartialEq for Book {
fn eq(&self, other: &Self) -> bool {
self.isbn == other.isbn
}
}
impl Eq for Book {}
```
这里只实现了 `PartialEq`,并没有实现 `Eq`,而是直接使用了默认实现 `impl Eq for Book {}`,奇了怪了,别急,还有呢:
```rust
impl PartialEq<IpAddr> for Ipv4Addr {
#[inline]
fn eq(&self, other: &IpAddr) -> bool {
match other {
IpAddr::V4(v4) => self == v4,
IpAddr::V6(_) => false,
}
}
}
impl Eq for Ipv4Addr {}
```
以上代码来自 Rust 标准库,可以看到,依然是这样使用,类似的情况数不胜数。既然如此,是否说明**如果要为我们的类型增加相等性比较,只要实现 `PartialEq` 即可?**
其实,关键点就在于 `partial` 上,**如果我们的类型只在部分情况下具有相等性**,那你就只能实现 `PartialEq`,否则可以实现 `PartialEq` 然后再默认实现 `Eq`
好的,问题逐步清晰起来,现在我们只需要搞清楚何为部分相等。
### 部分相等性
首先我们需要找到一个类型,它实现了 `PartialEq` 但是没有实现 `Eq`(你可能会想有没有反过来的情况?当然没有啦,部分相等肯定是全部相等的子集!)
`HashMap` 章节提到过 `HashMap` 的 key 要求实现 `Eq` 特征,也就是要能完全相等,而浮点数由于没有实现 `Eq` ,因此不能用于 `HashMap` 的 key。
当时由于一些知识点还没有介绍,因此就没有进一步展开,那么让我们考虑浮点数既然没有实现 `Eq` 为何还能进行比较呢?
```rust
fn main() {
let f1 = 3.14;
let f2 = 3.14;
if f1 == f2 {
println!("hello, world!");
}
}
```
以上代码是可以看到输出内容的,既然浮点数没有实现 `Eq` 那说明它实现了 `PartialEq`,一起写个简单代码验证下:
```rust
fn main() {
let f1 = 3.14;
is_eq(f1);
is_partial_eq(f1)
}
fn is_eq<T: Eq>(f: T) {}
fn is_partial_eq<T: PartialEq>(f: T) {}
```
上面的代码通过特征约束的方式验证了我们的结论:
```shell
3 | is_eq(f1);
| ----- ^^ the trait `Eq` is not implemented for `{float}`
```
好的,既然我们成功找到了一个类型实现了 `PartialEq` 但没有实现 `Eq`,那就通过它来看看何为部分相等性。
其实答案很简单,浮点数有一个特殊的值 `NaN`,它是无法进行相等性比较的:
```rust
fn main() {
let f1 = f32::NAN;
let f2 = f32::NAN;
if f1 == f2 {
println!("NaN 竟然可以比较,这很不数学啊!")
} else {
println!("果然,虽然两个都是 NaN ,但是它们其实并不相等")
}
}
```
大家猜猜哪一行会输出 :) 至于 `NaN` 为何不能比较,这个原因就比较复杂了( 有读者会说,其实就是你不知道,我只能义正严辞的说:咦?你怎么知道 :P )。
既然浮点数有一个值不可以比较相等性,那它自然只能实现 `PartialEq` 而不能实现 `Eq` 了,以此类推,如果我们的类型也有这种特殊要求,那也应该这么作。
### Ord 和 PartialOrd
事实上,还有一对与 `Eq/PartialEq` 非常类似的特征,它们可以用于 `<`、`<=`、`>` 和 `>=` 比较,至于哪个类型实现了 `PartialOrd` 却没有实现 `Ord` 就交给大家自己来思考了:)
> 小提示Ord 意味着一个类型的所有值都可以进行排序,而 PartialOrd 则不然

@ -1,5 +1,4 @@
# 疯狂字符串
字符串让人疯狂,这句话用在 Rust 中一点都不夸张,不信?那你能否清晰的说出 `String`、`str`、`&str`、`&String`、`Box<str>` 或 `Box<&str>` 的区别?
Rust 语言的类型可以大致分为两种:基本类型和标准库类型,前者是由语言特性直接提供的,而后者是在标准库中定义。即将登场的 `str` 类型就是唯一定义在语言特性中的字符串。
@ -7,7 +6,6 @@ Rust 语言的类型可以大致分为两种:基本类型和标准库类型,
> 在继续之前,大家需要先了解字符串的[基本知识](https://course.rs/basic/compound-type/string-slice.html),本文主要在于概念对比,而不是字符串讲解
## str
如上所述,`str` 是唯一定义在 Rust 语言特性中的字符串,但是也是我们几乎不会用到的字符串类型,为何?
原因在于 `str` 字符串它是 [`DST` 动态大小类型](https://course.rs/advance/custom-type.html#动态大小类型),这意味着编译器无法在编译期知道 `str` 类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
@ -17,7 +15,6 @@ let string: str = "banana";
```
上面代码创建一个 `str` 类型的字符串,看起来很正常,但是编译就会报错:
```shell
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:4:9
@ -28,14 +25,20 @@ error[E0277]: the size for values of type `str` cannot be known at compilation t
如果追求更深层的原因,我们可以总结如下:**所有的切片都是动态类型,它们都无法直接被使用,而 `str` 就是字符串切片,`[u8]` 是数组切片。**
同时还是 String 和 &str 的底层数据类型。 由于 str 是动态
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF8 编码**。
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` 和` CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
**未完待续**
https://pic1.zhimg.com/80/v2-177bce575bfaf289ae12d677689a26f4_1440w.png
https://pic2.zhimg.com/80/v2-697ad53cb502ccec4b2e98c40975344f_1440w.png
https://medium.com/@alisomay/strings-in-rust-28c08a2d3130
https://medium.com/@alisomay/strings-in-rust-28c08a2d3130

@ -16,7 +16,7 @@ Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目。从 2009 年开始
## 为何又来了一门新语言?
简而言之,因为还缺一门无 GC、性能高、工程性强、语言级安全性以及能同时得到工程派和学院派认可的语言,而 Rust 算是这样的语言。你也可以回忆下熟悉的语言,看是不是有另外一门语言可以同时满足这些需求:)
简而言之,**因为还缺一门无 GC 且无需手动内存管理、性能高、工程性强、语言级安全性以及能同时得到工程派和学院派认可的语言**,而 Rust 就是这样的语言。你也可以回忆下熟悉的语言,看是不是有另外一门语言可以同时满足这些需求:)
Rust 最为人诟病的点,也就一个:学习曲线陡峭。不过其实严格来说,当语言生态起来后,这个不算问题。

@ -57,7 +57,7 @@ pub trait Future {
代码中有几个关键点:
- [关联类型](https://course.rs/basic/trait/advance-trait.html#关联类型) `Output``Future` 执行完成后返回的值的类型
- `Pin` 类型是在异步函数中进行借用的关键,在[这里](<(https://course.rs/async/pin-unpin.html)>)有非常详细的介绍
- `Pin` 类型是在异步函数中进行借用的关键,在[这里](https://course.rs/async/pin-unpin.html)有非常详细的介绍
和其它语言不同Rust 中的 `Future` 不代表一个发生在后台的计算,而是 `Future` 就代表了计算本身,因此
`Future` 的所有者有责任去推进该计算过程的执行,例如通过 `Future::poll` 函数。听上去好像还挺复杂?但是大家不必担心,因为这些都在 Tokio 中帮你自动完成了 :)

@ -0,0 +1,90 @@
## 我们到底需不需要链表
经常有读者询问该如何实现一个链表,怎么说呢,这个答案主要取决于你的需求,因此并不是很好回答。鉴于此,我决定通过这本书来详尽的介绍该如何实现一个链表,大家应该都能从这本书中找到答案。
书中我们将通过实现 6 种链表来学习基本和进阶 Rust 编程知识,在此过程中,你能学到:
- 指针类型: `&`, `&mut`, `Box`, `Rc`, `Arc`, `*const`, `*mut`, `NonNull`
- 所有权、借用、继承可变性、内部可变性、Copy
- 所有的关键字struct、enum、fn、pub、impl、use, ...
- 模式匹配、泛型、解构
- 测试、安装新的工具链、使用 `miri`
- Unsafe: 裸指针、别名、栈借用、`UnsafeCell`、变体 variance
是的,链表就是这么可怕,只有将这些知识融会贯通后,你才能掌握 :(
> 事实上这本书中关于 Rust 语言的绝大部分知识都在 `Rust语言圣经`中有讲,因此除非特殊情况,我们将直接提供链接供大家学习,重点还是放在链表实现上
#### 创建一个项目
在开始前,先来创建一个项目专门用于链表学习:
```shell
> cargo new --lib lists
> cd lists
```
之后,我们会将每个一个链表放入单独的文件中,需要注意的是我们会尽量模拟真实的 Rust 开发场景:你写了一段代码,然后编译器开始跳出试图教你做事,只有这样才能真正学会 Rust温室环境是无法培养出强大的 Rustacean 的。
#### 义务告知
首先,本书不是保姆式教程,而且我个人认为编程应该是快乐,这种快乐往往需要你自己发现而不是别人的事无巨细的讲解。
其次,我讨厌链表。链表真的是一种糟糕的数据结构,尽管它在部分场景下确实很有用:
- 对列表进行大量的分割和合并操作
- 无锁并发
- 要实现内核或嵌入式的服务
- 你在使用一个纯函数式语言,由于受限的语法和缺少可变性,因此你需要使用链表来解决这些问题
但是实事求是的说,这些场景对于几乎任何 Rust 开发都是很少遇到的99% 的场景你可以使用 `Vec` 来替代,然后 1% 中的 99% 可以使用 `VecDeque`。 由于它们具有更少的内存分配次数、更低的内存占用、随机访问和缓存亲和特性,因此能够适用于绝大多数工作场景。总之,类似于 `trie` 树,链表也是一种非常小众的数据结构,特别是对于 Rust 开发而言。
> 本书只是为了学习链表该如何实现,如果大家只是为了使用链表,强烈推荐直接使用标准库或者社区提供的现成实现,例如 [std::collections::LinkedList](https://doc.rust-lang.org/std/collections/struct.LinkedList.html)
#### 链表有 O(1) 的分割、合并、插入、移除性能
是的,但是你首先要考虑的是,这些代码被调用的频率是怎么样的?是否在热点路径? 答案如果是否定的,那么还是强烈建议使用 `Vec` 等传统数据结构,况且整个数组的拷贝也是相当快的!
况且,`Vec` 上的 `push``pop` 操作是 `O(1)` 的,它们比链表提供的 `push``pop` 要更快!我们只需要通过一个指针 + 内存偏移就可以访问了。
> 关于是否使用链表这个问题Bjarne Stroustrup 有过非常深入的[讲解](https://www.youtube.com/watch?v=YQs6IC-vgmo)
但是如果你的整体项目确实因为某一段分割、合并的代码导致了性能低下,那么就放心大胆的使用链表吧。
#### 我无法接受内存重新分配的代价
是的,`Vec` 当 [`capacity`](https://practice.rs/collections/vector.html#capacity) 不够时,会重新分配一块内存,然后将之前的 `Vec` 全部拷贝过去,但是对于绝大多数使用场景,要么 `Vec` 不在热点路径中,要么 `Vec` 的容量可以提前预测。
对于前者,那性能如何自然无关紧要。而对于后者,我们只需要使用 `Vec::with_capacity` 提前分配足够的空间即可同时Rust 中所有的迭代器还提供了 `size_hint` 也可以解决这种问题。
当然,如果这段代码在热点路径,且你无法提前预测所需的容量,那么链表确实会更节省性能。
#### 链表更节省内存空间
首先,这个问题较为复杂。一个标准的数组调整策略是:增加或减少数组的长度使数组最多有一半为空,例如 capacity 增长是翻倍的策略。这确实会导致内存空间的浪费,特别是在 Rust 中,我们不会自动收缩集合类型。
但是上面说的是最坏的情况,如果是最好的情况,那整个数组其实只有 3 个指针大小(指针在 Rust 中占用一个 word 的空间,例如 64 位机器就是 8 个字节的大小)的内存浪费,或者说,没有浪费。
而且链表实际上也有内存浪费,例如链表中的每个元素都会占用额外的内存:单向链表浪费一个指针,双向链表浪费两个指针。当然,如果你的链表中每个元素都很大,那相对来说,这种浪费也微不足道,但是如果链表的元素较小且数量很多呢?那浪费的空间就相当可观了!
当然,这个也和使用的内存分配器有关( allocator ):对链表节点的分配和回收会经常发生,这样就不会浪费内存。
总之,如果链表的元素较大,你也无法预测数组的空间,同时还有一个不错的内存分配器,那链表确实可以节省空间!
#### 我在函数语言中一直使用链表
对于函数语言而言,链表确实非常棒,因为你可以解决可变性问题,还能递归地去使用,当然,可能还有一定的图方便的因素,因为链表不用操心长度等问题。
但彼之蜜糖不等于吾之蜜糖,函数语言的一些使用习惯不应该带入到其它语言中,例如 Rust。
- 函数语言往往将链表用于迭代,但是 Rust 中最适合迭代的数据结构是迭代器 `Iterator`
- 函数式语言的不可变对于 Rust 也不是问题
- Rust 还支持对数组进行切片以获取其中一部分连续的元素,而在函数语言中你可能得通过链表的 `head/tail` 分割来完成
其实,在函数语言中,我们也应该选择合适的数据结构来解决适合的场景,而不是*一根链表挂腰间,潇潇洒洒走天下*。
#### 链表适合构建并发数据结构
是这样的,如果有这样的需求,那么链表会非常合适!但是只有在你确实需要并发数据结构,且没有其它办法时,再考虑链表!
#### 链表非常适合教学目的
额... 这么说也没错,毕竟所有的编程语言课程都以链表来作为最常见的练手项目,包括本书也是服务于这个目的的。

@ -10,4 +10,5 @@
而本章,你就将见识到何为真正的深坑,看完后,就知道没有提早跳进去是一个多么幸运的事。总之,在专题中,你将学会如何使用 Rust 来实现链表。
**专题内容翻译自英文开源书 [Learning Rust With Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/),但是在内容上做了一些调整,希望大家喜欢。**
**专题内容翻译自英文开源书 [Learning Rust With Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/),但是在内容上做了一些调整(原书虽然非常棒,但是在一些内容组织和文字细节上我觉得还是可以优化下的 D),希望大家喜欢。**

@ -1,6 +1,19 @@
# ChangeLog
记录一些值得注意的变更。
## 2022-03-09
- 在 [Deref 章节](https://course.rs/advance/smart-pointer/deref.html)中新增开篇引导示例,帮助读者更好的理解当前章节
## 2022-03-08
- 新增章节: [我们到底需不需要链表](https://course.rs/too-many-lists/do-we-need-it)
## 2022-03-07
- 新增章节: [Eq 和 PartialEq](https://course.rs/confonding/eq.html)
## 2022-03-04
- 新增专题: [手把手带你实现链表](https://course.rs/linked-list/intro)

Loading…
Cancel
Save