Merge branch 'main' of github.com:dongchaoge/rust-course

pull/1366/head
keithMonster 10 months ago
commit 53e70706fe

@ -76,6 +76,13 @@
<table>
<tr>
<td align="center">
<a href="https://github.com/SUN-LG">
<img src="https://avatars.githubusercontent.com/u/15073915?v=4" width="100px" alt=""/>
<br />
<sub><b>孙立刚</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/JesseAtSZ">
<img src="https://avatars.githubusercontent.com/u/35264598?v=4?s=100" width="100px" alt=""/>
@ -97,13 +104,6 @@
<sub><b>1132719438</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/zongzi531">
<img src="https://avatars.githubusercontent.com/u/22429236?v=4?s=100" width="100px" alt=""/>
<br />
<sub><b>zongzi531</b></sub>
</a>
</td>
</tr>
</table>

@ -6,18 +6,13 @@
<!-- [快速查询入口](index-list.md) 暂时屏蔽-->
[社区和锈书](community.md)
---
[Datav: 可编程的数据可视化平台和可观测性平台](some-thoughts.md)
<!-- [一本生锈的书](rusty-book.md) -->
<!-- [Rust 语言周刊](rust-weekly.md) -->
<!-- [Rust 翻译计划( 代号 Rustt )](rustt.md) -->
# Rust 语言基础学习
---
@ -29,7 +24,6 @@
- [不仅仅是 Hello world](first-try/hello-world.md)
- [下载依赖太慢了?](first-try/slowly-downloading.md)
- [Rust 基础入门](basic/intro.md)
- [变量绑定与解构](basic/variable.md)
@ -135,7 +129,6 @@
- [多线程版本](advance-practice1/multi-threads.md)
- [优雅关闭和资源清理](advance-practice1/graceful-shutdown.md)
- [进阶实战2: 实现一个简单 Redis](advance-practice/intro.md)
- [tokio 概览](advance-practice/overview.md)
- [使用初印象](advance-practice/getting-startted.md)
@ -247,8 +240,19 @@
- [数据布局 2](too-many-lists/unsafe-queue/layout2.md)
- [额外的操作](too-many-lists/unsafe-queue/extra-junk.md)
- [最终代码](too-many-lists/unsafe-queue/final-code.md)
- [生产级的双向 unsafe 队列](too-many-lists/production-unsafe-deque/intro.md)
- [数据布局](too-many-lists/production-unsafe-deque/layout.md)
- [型变与子类型](too-many-lists/production-unsafe-deque/variance-and-phantomData.md)
- [基础结构](too-many-lists/production-unsafe-deque/basics.md)
- [恐慌与安全](too-many-lists/production-unsafe-deque/drop-and-panic-safety.md)
- [无聊的组合](too-many-lists/production-unsafe-deque/boring-combinatorics.md)
- [其它特征](too-many-lists/production-unsafe-deque/filling-in-random-bits.md)
- [测试](too-many-lists/production-unsafe-deque/testing.md)
- [Send,Sync和编译测试](too-many-lists/production-unsafe-deque/send-sync-and-compile-tests.md)
- [实现游标](too-many-lists/production-unsafe-deque/implementing-cursors.md)
- [测试游标](too-many-lists/production-unsafe-deque/testing-cursors.md)
- [最终代码](too-many-lists/production-unsafe-deque/final-code.md)
- [使用高级技巧实现链表](too-many-lists/advanced-lists/intro.md)
- [生产级可用的双向链表](too-many-lists/advanced-lists/unsafe-deque.md)
- [双单向链表](too-many-lists/advanced-lists/double-singly.md)
- [栈上的链表](too-many-lists/advanced-lists/stack-allocated.md)
@ -313,8 +317,6 @@
- [编译器优化 todo](profiling/compiler/optimization/intro.md)
- [Option 枚举 todo](profiling/compiler/optimization/option.md)
<!-- - [标准库解析 todo](std/intro.md)
- [标准库使用最佳实践 todo](std/search.md)

@ -303,7 +303,7 @@ where
}
```
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `&str` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://practice.rs/functional-programing/closure.html#closure-in-structs)
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `&str` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://zh-practice.course.rs/functional-programing/closure.html#closure-in-structs)
## 捕获作用域中的值
@ -812,4 +812,4 @@ fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> {
## 课后习题
> [Rust By Practice](https://practice.rs/functional-programing/closure.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/functional-programing/closure.md)。
> [Rust By Practice](https://zh-practice.course.rs/functional-programing/closure.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/functional-programing/closure.md)。

@ -324,9 +324,9 @@ unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R
## 课后练习
> Rust By Practice支持代码在线编辑和运行并提供详细的习题解答。
> - [as](https://zh.practice.rs/type-conversions/as.html)
> - [as](https://zh-practice.course.rs/type-conversions/as.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/type-conversions/as.md)
> - [From/Into](https://zh.practice.rs/type-conversions/from-into.html)
> - [From/Into](https://zh-practice.course.rs/type-conversions/from-into.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/type-conversions/from-into.md)
> - [其它转换](https://zh.practice.rs/type-conversions/others.html)
> - [其它转换](https://zh-practice.course.rs/type-conversions/others.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/type-conversions/others.md)

@ -132,7 +132,7 @@ error[E0499]: cannot borrow `*map` as mutable more than once at a time
分析代码可知在 `match map.get_mut(&key)` 方法调用完成后,对 `map` 的可变借用就可以结束了。但从报错看来,编译器不太聪明,它认为该借用会持续到整个 `match` 语句块的结束(第 16 行处),这便造成了后续借用的失败。
类似的例子还有很多,由于篇幅有限,就不在这里一一列举,如果大家想要阅读更多的类似代码,可以看看[<<Rust >>](https://github.com/sunface/rust-codes)一书。
类似的例子还有很多,由于篇幅有限,就不在这里一一列举,如果大家想要阅读更多的类似代码,可以看看[《Rust 代码鉴赏》](https://github.com/sunface/rust-codes)一书。
## 无界生命周期

@ -186,7 +186,7 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/lifetime/static.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://zh-practice.course.rs/lifetime/static.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
## 总结

@ -72,5 +72,5 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/basic-types/char-bool-unit.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/char-bool.md)。
> [Rust By Practice](https://zh-practice.course.rs/basic-types/char-bool-unit.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/char-bool.md)。

@ -125,7 +125,7 @@ fn main() {
例如单元类型 `()`,是一个零长度的元组。它没啥作用,但是可以用来表达一个函数没有返回值:
- 函数没有返回值,那么返回一个 `()`
- 通过 `;` 结尾的表达式返回一个 `()`
- 通过 `;` 结尾的语句返回一个 `()`
例如下面的 `report` 函数会隐式返回一个 `()`
@ -146,7 +146,7 @@ fn clear(text: &mut String) -> () {
}
```
在实际编程中,你会经常在错误提示中看到该 `()` 的身影出没,假如你的函数需要返回一个 `u32` 值,但是如果你不幸的以 `表达式;`式作为函数的最后一行代码,就会报错:
在实际编程中,你会经常在错误提示中看到该 `()` 的身影出没,假如你的函数需要返回一个 `u32` 值,但是如果你不幸的以 `表达式;`语句形式作为函数的最后一行代码,就会报错:
```rust
fn add(x:u32,y:u32) -> u32 {
@ -192,4 +192,4 @@ fn forever() -> ! {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/basic-types/functions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/functions.md)。
> [Rust By Practice](https://zh-practice.course.rs/basic-types/functions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/functions.md)。

@ -53,7 +53,12 @@ Rust 使用一个相对传统的语法来创建整数(`1``2`...)和浮
- 使用 `wrapping_*` 方法在所有模式下都按照补码循环溢出规则处理,例如 `wrapping_add`
- 如果使用 `checked_*` 方法时发生溢出,则返回 `None`
- 使用 `overflowing_*` 方法返回该值和一个指示是否存在溢出的布尔值
- 使用 `saturating_*` 方法使值达到最小值或最大值
- 使用 `saturating_*` 方法,可以限定计算后的结果不超过目标类型的最大值或低于最小值,例如:
```rust
assert_eq!(100u8.saturating_add(1), 101);
assert_eq!(u8::MAX.saturating_add(127), u8::MAX);
```
下面是一个演示`wrapping_*`方法的示例:
@ -240,13 +245,13 @@ fn main() {
Rust的位运算基本上和其他语言一样
| 运算符 | 说明 |
| ------- | -------------------------------------- |
| & 位与 | 相同位置均为1时则为1否则为0 |
| \| 位或 | 相同位置只要有1时则为1否则为0 |
| ^ 异或 | 相同位置不相同则为1相同则为0 |
| ! 位非 | 把位中的0和1相互取反即0置为11置为0 |
| << 左移 | 所有位向左移动指定位数右位补0 |
| 运算符 | 说明 |
| ------- | ------------------------------------------------------ |
| & 位与 | 相同位置均为1时则为1否则为0 |
| \| 位或 | 相同位置只要有1时则为1否则为0 |
| ^ 异或 | 相同位置不相同则为1相同则为0 |
| ! 位非 | 把位中的0和1相互取反即0置为11置为0 |
| << 左移 | 所有位向左移动指定位数右位补0 |
| >> 右移 | 所有位向右移动指定位数带符号移动正数补0负数补1 |
@ -351,7 +356,7 @@ use num::complex::Complex;
## 课后练习
> [Rust By Practice](https://zh.practice.rs/basic-types/numbers.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/numbers.md)。
> [Rust By Practice](https://zh-practice.course.rs/basic-types/numbers.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/numbers.md)。

@ -113,5 +113,5 @@ fn ret_unit_type() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/basic-types/statements-expressions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/statements.md)。
> [Rust By Practice](https://zh-practice.course.rs/basic-types/statements-expressions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/statements.md)。

@ -314,4 +314,4 @@ assert_eq!(hash.get(&42), Some(&"the answer"));
## 课后练习
> [Rust By Practice](https://zh.practice.rs/collections/hashmap.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Hashmap.md)。
> [Rust By Practice](https://zh-practice.course.rs/collections/hashmap.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Hashmap.md)。

@ -452,4 +452,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/collections/vector.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Vector.md)。
> [Rust By Practice](https://zh-practice.course.rs/collections/vector.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Vector.md)。

@ -472,4 +472,4 @@ Green
## 课后练习
> [Rust By Practice](https://zh.practice.rs/comments-docs.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://zh-practice.course.rs/comments-docs.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)

@ -216,4 +216,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/compound-types/array.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/array.md)。
> [Rust By Practice](https://zh-practice.course.rs/compound-types/array.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/array.md)。

@ -292,4 +292,4 @@ let none = plus_one(None);
## 课后练习
> [Rust By Practice](https://zh.practice.rs/compound-types/enum.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/enum.md)。
> [Rust By Practice](https://zh-practice.course.rs/compound-types/enum.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/enum.md)。

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

@ -426,5 +426,5 @@ $ cargo run
## 课后练习
> [Rust By Practice](https://zh.practice.rs/compound-types/struct.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/struct.md)。
> [Rust By Practice](https://zh-practice.course.rs/compound-types/struct.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/struct.md)。

@ -76,4 +76,4 @@ fn calculate_length(s: String) -> (String, usize) {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/compound-types/tuple.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/tuple.md)。
> [Rust By Practice](https://zh-practice.course.rs/compound-types/tuple.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/tuple.md)。

@ -111,4 +111,4 @@ error: a bin target must be available for `cargo run`
## 课后练习
> [Rust By Practice](https://zh.practice.rs/crate-module/crate.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/crate.md)。
> [Rust By Practice](https://zh-practice.course.rs/crate-module/crate.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/crate.md)。

@ -360,4 +360,4 @@ pub mod hosting;
## 课后练习
> [Rust By Practice](https://zh.practice.rs/crate-module/module.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/module.md)。
> [Rust By Practice](https://zh-practice.course.rs/crate-module/module.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/module.md)。

@ -468,4 +468,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/crate-module/use-pub.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/use-pub.md)。
> [Rust By Practice](https://zh-practice.course.rs/crate-module/use-pub.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/use-pub.md)。

@ -370,4 +370,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/flow-control.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/flow-control.md)。
> [Rust By Practice](https://zh-practice.course.rs/flow-control.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/flow-control.md)。

@ -462,7 +462,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
## 课后练习
> [Rust By Practice](https://zh.practice.rs/formatted-output.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://zh-practice.course.rs/formatted-output.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
## 总结

@ -476,7 +476,7 @@ help: consider using one of the available lifetimes here
| +++++++++
```
不得不说Rust 编译器真的很强大,还贴心的给我们提示了该如何修改,虽然。。。好像。。。。它的提示貌似不太准确。这里我们更希望参数和返回值都是 `'a` 生命周期。
不得不说Rust 编译器真的很强大,还贴心的给我们提示了该如何修改,虽然……好像……。它的提示貌似不太准确。这里我们更希望参数和返回值都是 `'a` 生命周期。
## 方法中的生命周期
@ -649,7 +649,7 @@ where
## 课后练习
> [Rust By Practice](https://zh.practice.rs/lifetime/basic.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://zh-practice.course.rs/lifetime/basic.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
## 总结

@ -644,4 +644,4 @@ num @ (1 | 2)
## 课后练习
> [Rust By Practice](https://zh.practice.rs/pattern-match/patterns.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/patterns.md)。
> [Rust By Practice](https://zh-practice.course.rs/pattern-match/patterns.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/patterns.md)。

@ -397,4 +397,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/pattern-match/match-iflet.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/match.md)。
> [Rust By Practice](https://zh-practice.course.rs/pattern-match/match-iflet.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/match.md)。

@ -279,4 +279,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/method.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/method.md)。
> [Rust By Practice](https://zh-practice.course.rs/method.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/method.md)。

@ -312,4 +312,4 @@ fn no_dangle() -> String {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/ownership/borrowing.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/ownership/borrowing.md)。
> [Rust By Practice](https://zh-practice.course.rs/ownership/borrowing.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/ownership/borrowing.md)。

@ -1,7 +1,7 @@
# 所有权和借用
Rust 之所以能成为万众瞩目的语言,就是因为其内存安全性。在以往,内存安全几乎都是通过 GC 的方式实现,但是 GC 会引来性能、内存占用以及 Stop the world 等问题,在高性能场景和系统编程上是不可接受的,因此 Rust 采用了与(错)众(误)不(之)同(源)的方式:**所有权系统**。
Rust 之所以能成为万众瞩目的语言,就是因为其内存安全性。在以往,内存安全几乎都是通过 GC 的方式实现,但是 GC 会引来性能、内存占用以及 Stop the world 等问题,在高性能场景和系统编程上是不可接受的,因此 Rust 采用了与 ( 不 ) 众 ( 咋 ) 不 ( 好 ) 同 ( 学 )的方式:**所有权系统**。
理解**所有权**和**借用**,对于 Rust 学习是至关重要的因此我们把本章提到了非常靠前的位置So骚年们,准备好迎接狂风暴雨了嘛?
理解**所有权**和**借用**,对于 Rust 学习是至关重要的因此我们把本章提到了非常靠前的位置So在座的各位,有一个算一个,准备好了嘛?
从现在开始,鉴于大家已经掌握了非常基本的语法,有些时候,在示例代码中,将省略 `fn main() {}` 的模版代码,只要将相应的示例放在 `fn main() {}` 中,即可运行。

@ -57,11 +57,8 @@ int* foo() {
#### 性能区别
写入方面:入栈比在堆上分配内存要快,因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备。
读取方面:得益于 CPU 高速缓存,使得处理器可以减少对内存的访问,高速缓存和内存的访问速度差异在 10 倍以上!栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。
因此,处理器处理分配在栈上数据会比在堆上的数据更加高效。
在栈上分配内存比在堆上分配内存要快,因为入栈时操作系统无需进行函数调用(或更慢的系统调用)来分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备,如果当前进程分配的内存页不足时,还需要进行系统调用来申请更多内存。
因此,处理器在栈上分配数据会比在堆上分配数据更加高效。
#### 所有权与堆栈
@ -117,7 +114,7 @@ let s = "hello";
let s = String::from("hello");
```
`::` 是一种调用操作符,这里表示调用 `String` 中的 `from` 方法,因为 `String` 存储在堆上是动态的,你可以这样修改它
`::` 是一种调用操作符,这里表示调用 `String` 模块中的 `from` 方法,由于 `String` 类型存储在堆上,因此它是动态的,你可以这样修改
```rust
let mut s = String::from("hello");
@ -127,7 +124,7 @@ s.push_str(", world!"); // push_str() 在字符串后追加字面值
println!("{}", s); // 将打印 `hello, world!`
```
言归正传,了解 `String` 内容后,一起来看看关于所有权的交互。
言归正传,了解 `String` 后,一起来看看关于所有权的交互。
## 变量绑定背后的数据交互
@ -140,9 +137,11 @@ let x = 5;
let y = x;
```
代码背后的逻辑很简单, 将 `5` 绑定到变量 `x`;接着拷贝 `x` 的值赋给 `y`,最终 `x``y` 都等于 `5`,因为整数是 Rust 基本数据类型,是固定大小的简单值,因此这两个值都是通过自动拷贝的方式来赋值的,都被存在栈中,完全无需在堆上分配内存。
这段代码并没有发生所有权的转移,原因很简单: 代码首先将 `5` 绑定到变量 `x`,接着**拷贝** `x` 的值赋给 `y`,最终 `x``y` 都等于 `5`,因为整数是 Rust 基本数据类型,是固定大小的简单值,因此这两个值都是通过**自动拷贝**的方式来赋值的,都被存在栈中,完全无需在堆上分配内存。
整个过程中的赋值都是通过值拷贝的方式完成(发生在栈中),因此并不需要所有权转移。
可能有同学会有疑问:这种拷贝不消耗性能吗?实际上,这种栈上的数据足够简单,而且拷贝非常非常快,只需要复制一个整数大小(`i32`4 个字节)的内存即可,因此在这种情况下,拷贝的速度远比在堆上创建内存来得快的多。实际上,上一章我们讲到的 Rust 基本类型都是通过自动拷贝的方式来赋值的,就像上面代码一样。
> 可能有同学会有疑问:这种拷贝不消耗性能吗?实际上,这种栈上的数据足够简单,而且拷贝非常非常快,只需要复制一个整数大小(`i32`4 个字节)的内存即可,因此在这种情况下,拷贝的速度远比在堆上创建内存来得快的多。实际上,上一章我们讲到的 Rust 基本类型都是通过自动拷贝的方式来赋值的,就像上面代码一样。
然后再来看一段代码:
@ -151,9 +150,9 @@ let s1 = String::from("hello");
let s2 = s1;
```
此时,可能某个大聪明(善意昵称)已经想到了:嗯,把 `s1` 的内容拷贝一份赋值给 `s2`实际上并不是这样。之前也提到了对于基本类型存储在栈上Rust 会自动拷贝,但是 `String` 不是基本类型,而且是存储在堆上的,因此不能自动拷贝。
此时,可能某个大聪明( 善意昵称 )已经想到了:嗯,上面一样,把 `s1` 的内容拷贝一份赋值给 `s2`实际上并不是这样。之前也提到了对于基本类型存储在栈上Rust 会自动拷贝,但是 `String` 不是基本类型,而且是存储在堆上的,因此不能自动拷贝。
实际上, `String` 类型是一个复杂类型,由**存储在栈中的堆指针**、**字符串长度**、**字符串容量**共同组成,其中**堆指针**是最重要的,它指向了真实存储字符串内容的堆内存,至于长度和容量,如果你有 Go 语言的经验,这里就很好理解:容量是堆内存分配空间的大小,长度是目前已经使用的大小。
实际上, `String` 类型是一个复杂类型,由存储在栈中的**堆指针**、**字符串长度**、**字符串容量**共同组成,其中**堆指针**是最重要的,它指向了真实存储字符串内容的堆内存,至于长度和容量,如果你有 Go 语言的经验,这里就很好理解:容量是堆内存分配空间的大小,长度是目前已经使用的大小。
总之 `String` 类型指向了一个堆上的空间,这里存储着它的真实数据,下面对上面代码中的 `let s2 = s1` 分成两种情况讨论:
@ -167,7 +166,7 @@ let s2 = s1;
当变量离开作用域后Rust 会自动调用 `drop` 函数并清理变量的堆内存。不过由于两个 `String` 变量指向了同一位置。这就有了一个问题:当 `s1``s2` 离开作用域,它们都会尝试释放相同的内存。这是一个叫做 **二次释放double free** 的错误,也是之前提到过的内存安全性 BUG 之一。两次释放(相同)内存会导致内存污染,它可能会导致潜在的安全漏洞。
因此Rust 这样解决问题:**当 `s1` 赋予 `s2`Rust 认为 `s1` 不再有效,因此也无需在 `s1` 离开作用域后 `drop` 任何东西,这就是把所有权从 `s1` 转移给了 `s2``s1` 在被赋予 `s2` 后就马上失效了**。
因此Rust 这样解决问题:**当 `s1` 赋予 `s2`Rust 认为 `s1` 不再有效,因此也无需在 `s1` 离开作用域后 `drop` 任何东西,这就是把所有权从 `s1` 转移给了 `s2``s1` 在被赋予 `s2` 后就马上失效了**。
再来看看,在所有权转移后再来使用旧的所有者,会发生什么:
@ -242,9 +241,9 @@ let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
```
这段代码能够正常运行,因此说明 `s2` 确实完整的复制了 `s1` 的数据。
这段代码能够正常运行,说明 `s2` 确实完整的复制了 `s1` 的数据。
如果代码性能无关紧要,例如初始化程序时,或者在某段时间只会执行一次时,你可以使用 `clone` 来简化编程。但是对于执行较为频繁的代码(热点路径),使用 `clone` 会极大的降低程序性能,需要小心使用!
如果代码性能无关紧要,例如初始化程序时或者在某段时间只会执行寥寥数次时,你可以使用 `clone` 来简化编程。但是对于执行较为频繁的代码(热点路径),使用 `clone` 会极大的降低程序性能,需要小心使用!
#### 拷贝(浅拷贝)
@ -263,9 +262,9 @@ println!("x = {}, y = {}", x, y);
原因是像整型这样的基本类型在编译时是已知大小的,会被存储在栈上,所以拷贝其实际的值是快速的。这意味着没有理由在创建变量 `y` 后使 `x` 无效(`x`、`y` 都仍然有效)。换句话说,这里没有深浅拷贝的区别,因此这里调用 `clone` 并不会与通常的浅拷贝有什么不同,我们可以不用管它(可以理解成在栈上做了深拷贝)。
Rust 有一个叫做 `Copy` 的特征,可以用在类似整型这样在栈中存储的类型。如果一个类型拥有 `Copy` 特征,一个旧的变量在被赋值给其他变量后仍然可用。
Rust 有一个叫做 `Copy` 的特征,可以用在类似整型这样在栈中存储的类型。如果一个类型拥有 `Copy` 特征,一个旧的变量在被赋值给其他变量后仍然可用,也就是赋值的过程即是拷贝的过程
那么什么类型是可 `Copy` 的呢?可以查看给定类型的文档来确认,不过作为一个通用的规则: **任何基本类型的组合可以 `Copy` ,不需要分配内存或某种形式资源的类型是可以 `Copy` 的**。如下是一些 `Copy` 的类型:
那么什么类型是可 `Copy` 的呢?可以查看给定类型的文档来确认,这里可以给出一个通用的规则: **任何基本类型的组合可以 `Copy` ,不需要分配内存或某种形式资源的类型是可以 `Copy` 的**。如下是一些 `Copy` 的类型:
- 所有整数类型,比如 `u32`
- 布尔类型,`bool`,它的值是 `true``false`
@ -339,5 +338,5 @@ fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用
## 课后练习
> [Rust By Practice](https://zh.practice.rs/ownership/ownership.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/ownership/ownership.md)。
> [Rust By Practice](https://zh-practice.course.rs/ownership/ownership.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/ownership/ownership.md)。

@ -214,4 +214,4 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
## 课后练习
> [Rust By Practice](https://zh.practice.rs/result-panic/panic.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/result-panic/panic.md)。
> [Rust By Practice](https://zh-practice.course.rs/result-panic/panic.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/result-panic/panic.md)。

@ -403,4 +403,4 @@ let x = try!(function_with_error());
## 课后练习
> [Rust By Practice](https://zh.practice.rs/result-panic/result.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/result-panic/result.md)。
> [Rust By Practice](https://zh-practice.course.rs/result-panic/result.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/result-panic/result.md)。

@ -427,4 +427,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/generics-traits/advanced-traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/advanced-trait.md)。
> [Rust By Practice](https://zh-practice.course.rs/generics-traits/advanced-traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/advanced-trait.md)。

@ -467,7 +467,7 @@ fn main() {
## 课后练习
> Rust By Practice支持代码在线编辑和运行并提供详细的习题解答。
> - [泛型](https://zh.practice.rs/generics-traits/generics.html)
> - [泛型](https://zh-practice.course.rs/generics-traits/generics.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/generics.md)
> - [const 泛型](https://zh.practice.rs/generics-traits/const-generics.html)
> - [const 泛型](https://zh-practice.course.rs/generics-traits/const-generics.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/const-generics.md)

@ -381,4 +381,4 @@ 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/blob/master/solutions/generics-traits/trait-object.md)。
> [Rust By Practice](https://zh-practice.course.rs/generics-traits/trait-object.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/trait-object.md)。

@ -580,4 +580,4 @@ fn main() {
## 课后练习
> [Rust By Practice](https://zh.practice.rs/generics-traits/traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/traits.md)。
> [Rust By Practice](https://zh-practice.course.rs/generics-traits/traits.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/traits.md)。

@ -259,5 +259,5 @@ error: aborting due to previous error
## 课后练习
> [Rust By Practice](https://zh.practice.rs/variables.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/variables.md)。
> [Rust By Practice](https://zh-practice.course.rs/variables.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/variables.md)。

@ -1,10 +1,10 @@
# 使用高级技巧实现链表
说句实话,我们之前实现的链表都达不到生产级可用的程度,而且也没有用到一些比较时髦的技巧。
本章我们一起来看一些更时髦的链表实现:
1. 生产级可用的双向链表
2. 双重单向链表
3. 栈分配的链表
4. 自引用和Arena分配器实现( 原文作者还未实现,所以... Todo )
5. GhostCell 实现( 同上 )
1. 双重单向链表
2. 栈分配的链表
3. 自引用和Arena分配器实现( 原文作者还未实现,所以... Todo )
4. GhostCell 实现( 同上 )

@ -49,7 +49,7 @@ $ cd lists
#### 我无法接受内存重新分配的代价
是的,`Vec` 当 [`capacity`](https://practice.rs/collections/vector.html#capacity) 不够时,会重新分配一块内存,然后将之前的 `Vec` 全部拷贝过去,但是对于绝大多数使用场景,要么 `Vec` 不在热点路径中,要么 `Vec` 的容量可以提前预测。
是的,`Vec` 当 [`capacity`](https://zh-practice.course.rs/collections/vector.html#capacity) 不够时,会重新分配一块内存,然后将之前的 `Vec` 全部拷贝过去,但是对于绝大多数使用场景,要么 `Vec` 不在热点路径中,要么 `Vec` 的容量可以提前预测。
对于前者,那性能如何自然无关紧要。而对于后者,我们只需要使用 `Vec::with_capacity` 提前分配足够的空间即可同时Rust 中所有的迭代器还提供了 `size_hint` 也可以解决这种问题。

@ -0,0 +1,169 @@
# Basics
好了,这就是本书最烂的部分,也是我花了 7 年时间才写完这一章的原因!是时候把我们已经做过 5 次的枯燥乏味的东西再写一遍了,但因为我们必须使用 Option<NonNull<Node<T>> 把每件事都做两遍,所以显得格外冗长!
```rust
impl<T> LinkedList<T> {
pub fn new() -> Self {
Self {
front: None,
back: None,
len: 0,
_boo: PhantomData,
}
}
}
```
PhantomData 是一种奇怪的类型,没有字段,所以你只需说出它的类型名称就能创建一个。
```rust
pub fn push_front(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
front: None,
back: None,
elem,
})));
if let Some(old) = self.front {
// Put the new front before the old one
(*old).front = Some(new);
(*new).back = Some(old);
} else {
// If there's no front, then we're the empty list and need
// to set the back too. Also here's some integrity checks
// for testing, in case we mess up.
debug_assert!(self.back.is_none());
debug_assert!(self.front.is_none());
debug_assert!(self.len == 0);
self.back = Some(new);
}
self.front = Some(new);
self.len += 1;
}
}
error[E0614]: type `NonNull<Node<T>>` cannot be dereferenced
--> src\lib.rs:39:17
|
39 | (*old).front = Some(new);
| ^^^^^^
```
是的,我真恨 `NonNull<Node<T>>`。我们需要明确地使用 `as_ptr` 从 NonNull 中获取原始指针,因为 DerefMut 是以 `&mut` 定义的,我们不想在不安全代码中随意引入安全引用!
```rust
(*old.as_ptr()).front = Some(new);
(*new.as_ptr()).back = Some(old);
Compiling linked-list v0.0.3
warning: field is never read: `elem`
--> src\lib.rs:16:5
|
16 | elem: T,
| ^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `linked-list` (lib) generated 1 warning (1 duplicate)
warning: `linked-list` (lib test) generated 1 warning
Finished test [unoptimized + debuginfo] target(s) in 0.33s
```
很好,接下来是 `pop``len`
```rust
pub fn pop_front(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a front node to pop.
// Note that we don't need to mess around with `take` anymore
// because everything is Copy and there are no dtors that will
// run if we mess up... right? :) Riiiight? :)))
self.front.map(|node| {
// Bring the Box back to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new front.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Cleanup its reference to the removed node
(*new.as_ptr()).front = None;
} else {
// If the front is now null, then this list is now empty!
debug_assert!(self.len == 1);
self.back = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn len(&self) -> usize {
self.len
}
Compiling linked-list v0.0.3
Finished dev [unoptimized + debuginfo] target(s) in 0.37s
```
在我看来是合法的,是时候写一个测试了!
```rust
#[cfg(test)]
mod test {
use super::LinkedList;
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Try to break an empty list
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Try to break a one item list
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Mess around
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
}
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.40s
Running unittests src\lib.rs
running 1 test
test test::test_basic_front ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```
万幸,我们是完美的!是吗?

@ -0,0 +1,565 @@
# Boring Combinatorics
好了,回到我们的常规链接列表!
首先,让我们来解决 `Drop` 的问题:
```rust
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
// Pop until we have to stop
while let Some(_) = self.pop_front() { }
}
}
```
我们必须填写一堆非常无聊的组合实现,如 front、front_mut、back、back_mut、iter、iter_mut、into_iter......
我已经精心设计了之前的 push/pop 实现,因此我们只需前后对调,代码就能做正确的事情!痛苦的经验万岁!(对于节点来说,使用 "prev和next "是很有诱惑力的,但我发现,为了避免错误,尽量使用 "front "和 "back"才是真正对的)。
首先是 `front`:
```rust
pub fn front(&self) -> Option<&T> {
unsafe {
self.front.map(|node| &(*node.as_ptr()).elem)
}
}
```
接着是:
```rust
pub fn front_mut(&mut self) -> Option<&mut T> {
unsafe {
self.front.map(|node| &mut (*node.as_ptr()).elem)
}
}
```
我会把所有的 `back` 版本放到文章最终的代码中。
接下来是迭代器。与之前的所有列表不同,我们终于解锁了双端迭代器([DoubleEndedIterator](https://doc.rust-lang.org/std/iter/trait.DoubleEndedIterator.html))的功能,而且如果要达到生产质量,我们还要支持精确大小迭代器( [ExactSizeIterator](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html))。
因此,除了 `next``size_hint`,我们还将支持 `next_back``len`
```rust
pub struct Iter<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a T>,
}
impl<T> LinkedList<T> {
pub fn iter(&self) -> Iter<T> {
Iter {
front: self.front,
back: self.back,
len: self.len,
_boo: PhantomData,
}
}
}
impl<'a, T> IntoIterator for &'a LinkedList<T> {
type IntoIter = Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
// While self.front == self.back is a tempting condition to check here,
// it won't do the right for yielding the last element! That sort of
// thing only works for arrays because of "one-past-the-end" pointers.
if self.len > 0 {
// We could unwrap front, but this is safer and easier
self.front.map(|node| unsafe {
self.len -= 1;
self.front = (*node.as_ptr()).back;
&(*node.as_ptr()).elem
})
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a, T> DoubleEndedIterator for Iter<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.len > 0 {
self.back.map(|node| unsafe {
self.len -= 1;
self.back = (*node.as_ptr()).front;
&(*node.as_ptr()).elem
})
} else {
None
}
}
}
impl<'a, T> ExactSizeIterator for Iter<'a, T> {
fn len(&self) -> usize {
self.len
}
}
```
...这只是 `.iter()`...
我们将在最后粘贴 IterMut它在很多地方与 `mut` 的代码完全相同,让我们先敲掉 `into_iter`。我们仍然可以使用我们屡试不爽的解决方案,即让它包裹我们的集合,并在下一步中使用 `pop`
```rust
pub struct IntoIter<T> {
list: LinkedList<T>,
}
impl<T> LinkedList<T> {
pub fn into_iter(self) -> IntoIter<T> {
IntoIter {
list: self
}
}
}
impl<T> IntoIterator for LinkedList<T> {
type IntoIter = IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.into_iter()
}
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.list.pop_front()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.list.len, Some(self.list.len))
}
}
impl<T> DoubleEndedIterator for IntoIter<T> {
fn next_back(&mut self) -> Option<Self::Item> {
self.list.pop_back()
}
}
impl<T> ExactSizeIterator for IntoIter<T> {
fn len(&self) -> usize {
self.list.len
}
}
```
仍然是一大堆模板,但至少是令人满意的模板。
好了,这是我们所有的代码,其中包含了所有的组合:
```rust
use std::ptr::NonNull;
use std::marker::PhantomData;
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<T>,
}
type Link<T> = Option<NonNull<Node<T>>>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
pub struct Iter<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a T>,
}
pub struct IterMut<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a mut T>,
}
pub struct IntoIter<T> {
list: LinkedList<T>,
}
impl<T> LinkedList<T> {
pub fn new() -> Self {
Self {
front: None,
back: None,
len: 0,
_boo: PhantomData,
}
}
pub fn push_front(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
front: None,
back: None,
elem,
})));
if let Some(old) = self.front {
// Put the new front before the old one
(*old.as_ptr()).front = Some(new);
(*new.as_ptr()).back = Some(old);
} else {
// If there's no front, then we're the empty list and need
// to set the back too.
self.back = Some(new);
}
// These things always happen!
self.front = Some(new);
self.len += 1;
}
}
pub fn push_back(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
back: None,
front: None,
elem,
})));
if let Some(old) = self.back {
// Put the new back before the old one
(*old.as_ptr()).back = Some(new);
(*new.as_ptr()).front = Some(old);
} else {
// If there's no back, then we're the empty list and need
// to set the front too.
self.front = Some(new);
}
// These things always happen!
self.back = Some(new);
self.len += 1;
}
}
pub fn pop_front(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a front node to pop.
self.front.map(|node| {
// Bring the Box back to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new front.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Cleanup its reference to the removed node
(*new.as_ptr()).front = None;
} else {
// If the front is now null, then this list is now empty!
self.back = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn pop_back(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a back node to pop.
self.back.map(|node| {
// Bring the Box front to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new back.
self.back = boxed_node.front;
if let Some(new) = self.back {
// Cleanup its reference to the removed node
(*new.as_ptr()).back = None;
} else {
// If the back is now null, then this list is now empty!
self.front = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn front(&self) -> Option<&T> {
unsafe {
self.front.map(|node| &(*node.as_ptr()).elem)
}
}
pub fn front_mut(&mut self) -> Option<&mut T> {
unsafe {
self.front.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn back(&self) -> Option<&T> {
unsafe {
self.back.map(|node| &(*node.as_ptr()).elem)
}
}
pub fn back_mut(&mut self) -> Option<&mut T> {
unsafe {
self.back.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn iter(&self) -> Iter<T> {
Iter {
front: self.front,
back: self.back,
len: self.len,
_boo: PhantomData,
}
}
pub fn iter_mut(&mut self) -> IterMut<T> {
IterMut {
front: self.front,
back: self.back,
len: self.len,
_boo: PhantomData,
}
}
pub fn into_iter(self) -> IntoIter<T> {
IntoIter {
list: self
}
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
// Pop until we have to stop
while let Some(_) = self.pop_front() { }
}
}
impl<'a, T> IntoIterator for &'a LinkedList<T> {
type IntoIter = Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
// While self.front == self.back is a tempting condition to check here,
// it won't do the right for yielding the last element! That sort of
// thing only works for arrays because of "one-past-the-end" pointers.
if self.len > 0 {
// We could unwrap front, but this is safer and easier
self.front.map(|node| unsafe {
self.len -= 1;
self.front = (*node.as_ptr()).back;
&(*node.as_ptr()).elem
})
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a, T> DoubleEndedIterator for Iter<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.len > 0 {
self.back.map(|node| unsafe {
self.len -= 1;
self.back = (*node.as_ptr()).front;
&(*node.as_ptr()).elem
})
} else {
None
}
}
}
impl<'a, T> ExactSizeIterator for Iter<'a, T> {
fn len(&self) -> usize {
self.len
}
}
impl<'a, T> IntoIterator for &'a mut LinkedList<T> {
type IntoIter = IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// While self.front == self.back is a tempting condition to check here,
// it won't do the right for yielding the last element! That sort of
// thing only works for arrays because of "one-past-the-end" pointers.
if self.len > 0 {
// We could unwrap front, but this is safer and easier
self.front.map(|node| unsafe {
self.len -= 1;
self.front = (*node.as_ptr()).back;
&mut (*node.as_ptr()).elem
})
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a, T> DoubleEndedIterator for IterMut<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.len > 0 {
self.back.map(|node| unsafe {
self.len -= 1;
self.back = (*node.as_ptr()).front;
&mut (*node.as_ptr()).elem
})
} else {
None
}
}
}
impl<'a, T> ExactSizeIterator for IterMut<'a, T> {
fn len(&self) -> usize {
self.len
}
}
impl<T> IntoIterator for LinkedList<T> {
type IntoIter = IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.into_iter()
}
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.list.pop_front()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.list.len, Some(self.list.len))
}
}
impl<T> DoubleEndedIterator for IntoIter<T> {
fn next_back(&mut self) -> Option<Self::Item> {
self.list.pop_back()
}
}
impl<T> ExactSizeIterator for IntoIter<T> {
fn len(&self) -> usize {
self.list.len
}
}
#[cfg(test)]
mod test {
use super::LinkedList;
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Try to break an empty list
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Try to break a one item list
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Mess around
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
}
```

@ -0,0 +1,162 @@
# Drop and Panic Safety
嘿,你注意到这些注释了吗:
```rust
// Note that we don't need to mess around with `take` anymore
// because everything is Copy and there are no dtors that will
// run if we mess up... right? :) Riiiight? :)))
```
这对吗?
你忘记你正在读那本书了吗?当然这是错误的(部分上是)。
让我们再次看看 pop_front 内部:
```rust
// Bring the Box back to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new front.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Cleanup its reference to the removed node
(*new.as_ptr()).front = None;
} else {
// If the front is now null, then this list is now empty!
debug_assert!(self.len == 1);
self.back = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
```
你看到 bug 了吗? 真可怕, 是这一行:
```rust
debug_assert!(self.len == 1);
```
大多数情况下,你不需要考虑或担心恐慌,但一旦你开始编写真正不安全的代码,并在 "invariants(不可变性) "上大做文章,你就需要对恐慌保持高度警惕!
我们必须谈谈 [*异常安全*](https://doc.rust-lang.org/nightly/nomicon/exception-safety.html) (又名恐慌安全、解除安全......)。
情况是这样的:在默认情况下,恐慌会被 unwinding。unwind 只是 "让每个函数立即返回 "的一种花哨说法。你可能会想:"好吧,如果每个函数都返回,那么程序就要结束了,何必在乎它呢?"但你错了!
我们必须关注有两个原因:当函数返回时,析构函数会运行,而且可以捕获 unwind。在这两种情况下代码都可能在恐慌发生后继续运行因此我们必须非常小心确保我们的不安全的集合在恐慌发生时始终处于某种一致的状态因为每次恐慌都是隐式的提前返回
让我们想一想,到这一行时,我们的集合处于什么状态:
我们将 boxed_node 放在栈上并从中提取了元素。如果我们在此时返回Box 将被丢弃节点将被释放。self.back 仍然指向那个被释放的节点!一旦我们使用 self.back 来处理一些事情,这就可能导致释放后再使用!
有趣的是,这行也有类似的问题,但它要安全得多:
```rust
self.len -= 1;
```
默认情况下Rust 会在调试构建时检查上溢和下溢,并在发生时产生恐慌。是的,每一次算术运算都会带来恐慌安全隐患!这行还好,他不会导致内存错误,因为之前已经完成了该做的所有操作。所以调试断言哪行在某种意义上更糟糕,因为它可能将一个小问题升级为关键问题!
在实现过程中只要我们确保在别人注意到之前修复它们我们可以临时性的破坏invariants(不可变性)。这实际上是 Rust 的集合所有权和借用系统的 "杀手级应用 "之一:如果一个操作需要一个 `&mut Self`那么我们就能保证对我们的集合拥有独占访问权而且我们可以暂时破坏invariants(不可变性),因为我们知道没有人能偷偷摸摸地破坏它。
我们有两种方法可以让我们的代码更健壮:
- 更积极地使用 Option::take 这样的操作,因为它们更 "事务性"更倾向于保留invariants(不可变性)。
- 放弃 debug_asserts相信自己能写出更好的测试并使用专用的 "完整性检查 "函数,而这些函数永远不会在用户代码中运行。
原则上我喜欢第一种方案但它对双链路列表的实际效果并不好因为所有内容都是双冗余编码的。Option::take 并不能解决这里的问题,但将 debug_assert 下移一行却可以。不过说真的,为什么要为难我们自己呢?让我们移除那些 debug_asserts并确保任何可能引起恐慌的事情都发生在我们方法的开头或结尾而我们在这些地方保持invariants(不可变性)。
这是我们的全部实现:
```rust
use std::ptr::NonNull;
use std::marker::PhantomData;
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<T>,
}
type Link<T> = Option<NonNull<Node<T>>>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
impl<T> LinkedList<T> {
pub fn new() -> Self {
Self {
front: None,
back: None,
len: 0,
_boo: PhantomData,
}
}
pub fn push_front(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
front: None,
back: None,
elem,
})));
if let Some(old) = self.front {
// Put the new front before the old one
(*old.as_ptr()).front = Some(new);
(*new.as_ptr()).back = Some(old);
} else {
// If there's no front, then we're the empty list and need
// to set the back too.
self.back = Some(new);
}
// These things always happen!
self.front = Some(new);
self.len += 1;
}
}
pub fn pop_front(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a front node to pop.
self.front.map(|node| {
// Bring the Box back to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new front.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Cleanup its reference to the removed node
(*new.as_ptr()).front = None;
} else {
// If the front is now null, then this list is now empty!
self.back = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn len(&self) -> usize {
self.len
}
}
```
这还有什么可以引发恐慌?老实说,要知道这些需要你是 Rust 专家,不过幸好我是!
在这段代码中,我能看到的唯一可能引起恐慌的地方是 `Box::new`(用于内存不足的情况)和 `len` 运算。所有这些都在我们方法的最末端或最开始,所以,我们是安全的!

@ -0,0 +1,572 @@
# Filling In Random Bits
嘿,你不是说要做成精品吗?
为了成为一个 "好 "系列,这里还有一些乱七八糟的东西:
```rust
impl<T> LinkedList<T> {
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn clear(&mut self) {
// Oh look it's drop again
while let Some(_) = self.pop_front() { }
}
}
```
现在,我们已经有了一大堆大家都期待的特性需要实现:
```rust
impl<T> Default for LinkedList<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Clone> Clone for LinkedList<T> {
fn clone(&self) -> Self {
let mut new_list = Self::new();
for item in self {
new_list.push_back(item.clone());
}
new_list
}
}
impl<T> Extend<T> for LinkedList<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for item in iter {
self.push_back(item);
}
}
}
impl<T> FromIterator<T> for LinkedList<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut list = Self::new();
list.extend(iter);
list
}
}
impl<T: Debug> Debug for LinkedList<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self).finish()
}
}
impl<T: PartialEq> PartialEq for LinkedList<T> {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().eq(other)
}
fn ne(&self, other: &Self) -> bool {
self.len() != other.len() || self.iter().ne(other)
}
}
impl<T: Eq> Eq for LinkedList<T> { }
impl<T: PartialOrd> PartialOrd for LinkedList<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.iter().partial_cmp(other)
}
}
impl<T: Ord> Ord for LinkedList<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.iter().cmp(other)
}
}
impl<T: Hash> Hash for LinkedList<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.len().hash(state);
for item in self {
item.hash(state);
}
}
}
```
另一个有趣的话题是哈希本身。你看到我们如何将 `len` 写入散列的吗?这其实非常重要!如果集合不把 `len` 加入散列,很可能会意外的造成前缀碰撞。例如,一个集合包含 `["he", "llo"]` 另一个集合包含 `["hello"]`,我们该如何区分?如果没有把集合长度或其它"分隔符"加入到散列 ,这将毫无意义!会让意外哈希碰撞发生变得太容易,会导致严重的后果,所以还是照做吧!
好了,这是我们现在的代码:
```rust
use std::cmp::Ordering;
use std::fmt::{self, Debug};
use std::hash::{Hash, Hasher};
use std::iter::FromIterator;
use std::ptr::NonNull;
use std::marker::PhantomData;
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<T>,
}
type Link<T> = Option<NonNull<Node<T>>>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
pub struct Iter<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a T>,
}
pub struct IterMut<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a mut T>,
}
pub struct IntoIter<T> {
list: LinkedList<T>,
}
impl<T> LinkedList<T> {
pub fn new() -> Self {
Self {
front: None,
back: None,
len: 0,
_boo: PhantomData,
}
}
pub fn push_front(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
front: None,
back: None,
elem,
})));
if let Some(old) = self.front {
// Put the new front before the old one
(*old.as_ptr()).front = Some(new);
(*new.as_ptr()).back = Some(old);
} else {
// If there's no front, then we're the empty list and need
// to set the back too.
self.back = Some(new);
}
// These things always happen!
self.front = Some(new);
self.len += 1;
}
}
pub fn push_back(&mut self, elem: T) {
// SAFETY: it's a linked-list, what do you want?
unsafe {
let new = NonNull::new_unchecked(Box::into_raw(Box::new(Node {
back: None,
front: None,
elem,
})));
if let Some(old) = self.back {
// Put the new back before the old one
(*old.as_ptr()).back = Some(new);
(*new.as_ptr()).front = Some(old);
} else {
// If there's no back, then we're the empty list and need
// to set the front too.
self.front = Some(new);
}
// These things always happen!
self.back = Some(new);
self.len += 1;
}
}
pub fn pop_front(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a front node to pop.
self.front.map(|node| {
// Bring the Box back to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new front.
self.front = boxed_node.back;
if let Some(new) = self.front {
// Cleanup its reference to the removed node
(*new.as_ptr()).front = None;
} else {
// If the front is now null, then this list is now empty!
self.back = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn pop_back(&mut self) -> Option<T> {
unsafe {
// Only have to do stuff if there is a back node to pop.
self.back.map(|node| {
// Bring the Box front to life so we can move out its value and
// Drop it (Box continues to magically understand this for us).
let boxed_node = Box::from_raw(node.as_ptr());
let result = boxed_node.elem;
// Make the next node into the new back.
self.back = boxed_node.front;
if let Some(new) = self.back {
// Cleanup its reference to the removed node
(*new.as_ptr()).back = None;
} else {
// If the back is now null, then this list is now empty!
self.front = None;
}
self.len -= 1;
result
// Box gets implicitly freed here, knows there is no T.
})
}
}
pub fn front(&self) -> Option<&T> {
unsafe {
self.front.map(|node| &(*node.as_ptr()).elem)
}
}
pub fn front_mut(&mut self) -> Option<&mut T> {
unsafe {
self.front.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn back(&self) -> Option<&T> {
unsafe {
self.back.map(|node| &(*node.as_ptr()).elem)
}
}
pub fn back_mut(&mut self) -> Option<&mut T> {
unsafe {
self.back.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn clear(&mut self) {
// Oh look it's drop again
while let Some(_) = self.pop_front() { }
}
pub fn iter(&self) -> Iter<T> {
Iter {
front: self.front,
back: self.back,
len: self.len,
_boo: PhantomData,
}
}
pub fn iter_mut(&mut self) -> IterMut<T> {
IterMut {
front: self.front,
back: self.back,
len: self.len,
_boo: PhantomData,
}
}
pub fn into_iter(self) -> IntoIter<T> {
IntoIter {
list: self
}
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
// Pop until we have to stop
while let Some(_) = self.pop_front() { }
}
}
impl<T> Default for LinkedList<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Clone> Clone for LinkedList<T> {
fn clone(&self) -> Self {
let mut new_list = Self::new();
for item in self {
new_list.push_back(item.clone());
}
new_list
}
}
impl<T> Extend<T> for LinkedList<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for item in iter {
self.push_back(item);
}
}
}
impl<T> FromIterator<T> for LinkedList<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut list = Self::new();
list.extend(iter);
list
}
}
impl<T: Debug> Debug for LinkedList<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self).finish()
}
}
impl<T: PartialEq> PartialEq for LinkedList<T> {
fn eq(&self, other: &Self) -> bool {
self.len() == other.len() && self.iter().eq(other)
}
fn ne(&self, other: &Self) -> bool {
self.len() != other.len() || self.iter().ne(other)
}
}
impl<T: Eq> Eq for LinkedList<T> { }
impl<T: PartialOrd> PartialOrd for LinkedList<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.iter().partial_cmp(other)
}
}
impl<T: Ord> Ord for LinkedList<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.iter().cmp(other)
}
}
impl<T: Hash> Hash for LinkedList<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.len().hash(state);
for item in self {
item.hash(state);
}
}
}
impl<'a, T> IntoIterator for &'a LinkedList<T> {
type IntoIter = Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
// While self.front == self.back is a tempting condition to check here,
// it won't do the right for yielding the last element! That sort of
// thing only works for arrays because of "one-past-the-end" pointers.
if self.len > 0 {
// We could unwrap front, but this is safer and easier
self.front.map(|node| unsafe {
self.len -= 1;
self.front = (*node.as_ptr()).back;
&(*node.as_ptr()).elem
})
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a, T> DoubleEndedIterator for Iter<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.len > 0 {
self.back.map(|node| unsafe {
self.len -= 1;
self.back = (*node.as_ptr()).front;
&(*node.as_ptr()).elem
})
} else {
None
}
}
}
impl<'a, T> ExactSizeIterator for Iter<'a, T> {
fn len(&self) -> usize {
self.len
}
}
impl<'a, T> IntoIterator for &'a mut LinkedList<T> {
type IntoIter = IterMut<'a, T>;
type Item = &'a mut T;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
// While self.front == self.back is a tempting condition to check here,
// it won't do the right for yielding the last element! That sort of
// thing only works for arrays because of "one-past-the-end" pointers.
if self.len > 0 {
// We could unwrap front, but this is safer and easier
self.front.map(|node| unsafe {
self.len -= 1;
self.front = (*node.as_ptr()).back;
&mut (*node.as_ptr()).elem
})
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.len, Some(self.len))
}
}
impl<'a, T> DoubleEndedIterator for IterMut<'a, T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.len > 0 {
self.back.map(|node| unsafe {
self.len -= 1;
self.back = (*node.as_ptr()).front;
&mut (*node.as_ptr()).elem
})
} else {
None
}
}
}
impl<'a, T> ExactSizeIterator for IterMut<'a, T> {
fn len(&self) -> usize {
self.len
}
}
impl<T> IntoIterator for LinkedList<T> {
type IntoIter = IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
self.into_iter()
}
}
impl<T> Iterator for IntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.list.pop_front()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.list.len, Some(self.list.len))
}
}
impl<T> DoubleEndedIterator for IntoIter<T> {
fn next_back(&mut self) -> Option<Self::Item> {
self.list.pop_back()
}
}
impl<T> ExactSizeIterator for IntoIter<T> {
fn len(&self) -> usize {
self.list.len
}
}
#[cfg(test)]
mod test {
use super::LinkedList;
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Try to break an empty list
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Try to break a one item list
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Mess around
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
}
```

File diff suppressed because it is too large Load Diff

@ -0,0 +1,767 @@
# Implementing Cursors
好了,我们现在讨论 CursorMut。就像我最初的设计一样它有一个包含 None 的 "幽灵 "元素,用来指示列表的开始/结束,你可以 "跨过它",绕到列表的另一边。要实现它,我们需要
- 指向当前节点的指针
- 指向列表的指针
- 当前索引
等等,当我们指向 "幽灵 "时,索引是多少?
好吧,游标 (cursors)上的索引返回一个 `Option<usize>`这很合理。Std 的实现做了一堆垃圾来避免将其存储为一个 Option但是...... 我们是一个链接列表这很好。此外std 还有 cursor_front/cursor_back 功能,它可以在前/后元素上启动光标,感觉很直观,但当列表为空时,又要做一些奇怪的事情。
如果你愿意,也可以实现这些东西,但我打算减少所有重复的垃圾和角落情况,只做一个从 ghost 处开始的 cursor_mut 方法,人们可以使用 move_next/move_prev 来获取他们想要的元素(如果你真的愿意,也可以将其封装为 cursor_front
让我们开始吧:
非常简单直接,上面的需求列表每一项都有一个字段!
```rust
pub struct CursorMut<'a, T> {
cur: Link<T>,
list: &'a mut LinkedList<T>,
index: Option<usize>,
}
```
现在是`cursor_mut` 方法:
```rust
impl<T> LinkedList<T> {
pub fn cursor_mut(&mut self) -> CursorMut<T> {
CursorMut {
list: self,
cur: None,
index: None,
}
}
}
```
既然我们从幽灵节点开始,我们所以开始节点都是 `None`,简单明了!下一个是 `move_next`
```rust
impl<'a, T> CursorMut<'a, T> {
pub fn index(&self) -> Option<usize> {
self.index
}
pub fn move_next(&mut self) {
if let Some(cur) = self.cur {
unsafe {
// We're on a real element, go to its next (back)
self.cur = (*cur.as_ptr()).back;
if self.cur.is_some() {
*self.index.as_mut().unwrap() += 1;
} else {
// We just walked to the ghost, no more index
self.index = None;
}
}
} else if !self.list.is_empty() {
// We're at the ghost, and there is a real front, so move to it!
self.cur = self.list.front;
self.index = Some(0)
} else {
// We're at the ghost, but that's the only element... do nothing.
}
}
}
```
所以这有4种有趣的情况
- 正常情况
- 正常情况,但我们移动到了幽灵节点
- 幽灵节点开始,向列表头部节点移动
- 幽灵节点开始,列表是空的,所以什么都不做
`move_prev` 的逻辑完全相同,但前后颠倒,索引变化也颠倒:
```rust
pub fn move_prev(&mut self) {
if let Some(cur) = self.cur {
unsafe {
// We're on a real element, go to its previous (front)
self.cur = (*cur.as_ptr()).front;
if self.cur.is_some() {
*self.index.as_mut().unwrap() -= 1;
} else {
// We just walked to the ghost, no more index
self.index = None;
}
}
} else if !self.list.is_empty() {
// We're at the ghost, and there is a real back, so move to it!
self.cur = self.list.back;
self.index = Some(self.list.len - 1)
} else {
// We're at the ghost, but that's the only element... do nothing.
}
}
```
接下来,让我们添加一些方法来查看游标周围的元素:`current`、`peek_next` 和 `peek_prev`**一个非常重要的注意事项**:这些方法必须通过 `&mut self` 借用我们的游标,并且结果必须与借用绑定。我们不能让用户获得可变引用的多个副本,也不能让他们在持有该引用的情况下使用我们的 insert/remove/split/splice API
值得庆幸的是,这是 rust 在使用生命周期省略规则时的默认设置,因此我们将默认做正确的事情!
```rust
pub fn current(&mut self) -> Option<&mut T> {
unsafe {
self.cur.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_next(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).back)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_prev(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).front)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
```
# [Split](https://rust-unofficial.github.io/too-many-lists/sixth-cursors-impl.html#split)
首先是 split_before 和 split_after它们会将当前元素之前/之后的所有内容以 LinkedList 的形式返回(在幽灵元素处停止,在这种情况下,我们只返回整个 List光标现在指向一个空 list
这个逻辑其实并不复杂,所以我们得一步一步来。
我发现 split_before 有四种潜在的情况:
- 正常情况
- 正常情况,但 prev 是幽灵节点
- 幽灵节点情况,我们返回整个列表,然后变成空列表
- 幽灵节点情况,但列表是空的,所以什么也不做,返回空列表
让我们先从极端情况开始。我认为第三种情况
```rust
mem::replace(self.list, LinkedList::new())
```
对不对?我们是空的了,并返回了整个列表,而我们的字段都应该是 "None",所以没什么可更新的。不错。哦,嘿嘿,这在第四种情况下也对!
现在是普通情况......,我需要画下图。最常见的情况是这样的
```text
list.front -> A <-> B <-> C <-> D <- list.back
^
cur
```
我们想变成这样:
```text
list.front -> C <-> D <- list.back
^
cur
return.front -> A <-> B <- return.back
```
因此,我们需要打破当前数据和前一个数据之间的联系,而且......天哪,需要改变的东西太多了。好吧,我只需要把它分成几个步骤,这样我就能说服自己这是有意义的。虽然有点啰嗦,但我至少能说得通:
```rust
pub fn split_before(&mut self) -> LinkedList<T> {
if let Some(cur) = self.cur {
// We are pointing at a real element, so the list is non-empty.
unsafe {
// Current state
let old_len = self.list.len;
let old_idx = self.index.unwrap();
let prev = (*cur.as_ptr()).front;
// What self will become
let new_len = old_len - old_idx;
let new_front = self.cur;
let new_idx = Some(0);
// What the output will become
let output_len = old_len - new_len;
let output_front = self.list.front;
let output_back = prev;
// Break the links between cur and prev
if let Some(prev) = prev {
(*cur.as_ptr()).front = None;
(*prev.as_ptr()).back = None;
}
// Produce the result:
self.list.len = new_len;
self.list.front = new_front;
self.index = new_idx;
LinkedList {
front: output_front,
back: output_back,
len: output_len,
_boo: PhantomData,
}
}
} else {
// We're at the ghost, just replace our list with an empty one.
// No other state needs to be changed.
std::mem::replace(self.list, LinkedList::new())
}
}
```
你可能注意到,我们没有处理 prev 是幽灵节点的情况。但据我所知,其他一切都只是顺便做了正确的事。等我们写测试的时候就知道了!(复制粘贴完成 split_after
# [Splice](https://rust-unofficial.github.io/too-many-lists/sixth-cursors-impl.html#splice)
还有一个老大难,那就是 splice_before 和 splice_after我估计这是最容易出错的一个。这两个函数接收一个 LinkedList并将其内容嫁接到我们的列表中。我们的列表可能是空的他们的列表也可能是空的我们还有幽灵节点要处理......叹口气,让我们一步一步来吧,从 splice_before 开始。
- 如果他们的列表是空的,我们就什么都不用做。
- 如果我们的列表是空的,那么我们的列表就变成了他们的列表。
- 如果我们指向的是幽灵节点,则追加到后面(更改 list.back
- 如果我们指向的是第一个元素0则追加到前面更改 list.front
- 一般情况下,我们会进行大量的指针操作
一般情况:
```text
input.front -> 1 <-> 2 <- input.back
list.front -> A <-> B <-> C <- list.back
^
cur
```
变成这样:
```text
list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back
```
好的,让我们来写一下:
```rust
pub fn splice_before(&mut self, mut input: LinkedList<T>) {
unsafe {
if input.is_empty() {
// Input is empty, do nothing.
} else if let Some(cur) = self.cur {
if let Some(0) = self.index {
// We're appending to the front, see append to back
(*cur.as_ptr()).front = input.back.take();
(*input.back.unwrap().as_ptr()).back = Some(cur);
self.list.front = input.front.take();
// Index moves forward by input length
*self.index.as_mut().unwrap() += input.len;
self.list.len += input.len;
input.len = 0;
} else {
// General Case, no boundaries, just internal fixups
let prev = (*cur.as_ptr()).front.unwrap();
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
(*prev.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(prev);
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
// Index moves forward by input length
*self.index.as_mut().unwrap() += input.len;
self.list.len += input.len;
input.len = 0;
}
} else if let Some(back) = self.list.back {
// We're on the ghost but non-empty, append to the back
// We can either `take` the input's pointers or `mem::forget`
// it. Using take is more responsible in case we do custom
// allocators or something that also needs to be cleaned up!
(*back.as_ptr()).back = input.front.take();
(*input.front.unwrap().as_ptr()).front = Some(back);
self.list.back = input.back.take();
self.list.len += input.len;
// Not necessary but Polite To Do
input.len = 0;
} else {
// We're empty, become the input, remain on the ghost
*self.list = input;
}
}
}
```
好吧,这个程序真的很可怕,现在真的感觉到 Option<NonNull> 的痛苦了。但我们可以做很多清理工作。首先,我们可以把这段代码拖到最后。
```rust
self.list.len += input.len;
input.len = 0;
```
好了,现在在分支 "we're empty" 中有以下错误。所以我们应该使用 `swap`:
> Use of moved value: `input`
```rust
// We're empty, become the input, remain on the ghost
std::mem::swap(self.list, &mut input);
```
在我反向思考下面这种情况时,我发现了这个 `unwrap` 有问题(因为 cur 的 front 在前面已经被设置为其它值了)
```rust
if let Some(0) = self.index {
} else {
let prev = (*cur.as_ptr()).front.unwrap();
}
```
这行也是重复的,可以提升:
```rust
*self.index.as_mut().unwrap() += input.len;
```
好了,把上面的问题修改后得到这些:
```rust
if input.is_empty() {
// Input is empty, do nothing.
} else if let Some(cur) = self.cur {
// Both lists are non-empty
if let Some(prev) = (*cur.as_ptr()).front {
// General Case, no boundaries, just internal fixups
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
(*prev.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(prev);
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
} else {
// We're appending to the front, see append to back below
(*cur.as_ptr()).front = input.back.take();
(*input.back.unwrap().as_ptr()).back = Some(cur);
self.list.front = input.front.take();
}
// Index moves forward by input length
*self.index.as_mut().unwrap() += input.len;
} else if let Some(back) = self.list.back {
// We're on the ghost but non-empty, append to the back
// We can either `take` the input's pointers or `mem::forget`
// it. Using take is more responsible in case we do custom
// allocators or something that also needs to be cleaned up!
(*back.as_ptr()).back = input.front.take();
(*input.front.unwrap().as_ptr()).front = Some(back);
self.list.back = input.back.take();
} else {
// We're empty, become the input, remain on the ghost
std::mem::swap(self.list, &mut input);
}
self.list.len += input.len;
// Not necessary but Polite To Do
input.len = 0;
// Input dropped here
```
还是不对下面的代码存在bug
```rust
(*back.as_ptr()).back = input.front.take();
(*input.front.unwrap().as_ptr()).front = Some(back);
```
我们使用 `take` 拿走了 input.front 的值,然后在下一行将其 `unwrap`boompanic
```rust
// We can either `take` the input's pointers or `mem::forget`
// it. Using `take` is more responsible in case we ever do custom
// allocators or something that also needs to be cleaned up!
if input.is_empty() {
// Input is empty, do nothing.
} else if let Some(cur) = self.cur {
// Both lists are non-empty
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
if let Some(prev) = (*cur.as_ptr()).front {
// General Case, no boundaries, just internal fixups
(*prev.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(prev);
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
} else {
// No prev, we're appending to the front
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
self.list.front = Some(in_front);
}
// Index moves forward by input length
*self.index.as_mut().unwrap() += input.len;
} else if let Some(back) = self.list.back {
// We're on the ghost but non-empty, append to the back
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
(*back.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(back);
self.list.back = Some(in_back);
} else {
// We're empty, become the input, remain on the ghost
std::mem::swap(self.list, &mut input);
}
self.list.len += input.len;
// Not necessary but Polite To Do
input.len = 0;
// Input dropped here
```
总之,我已经筋疲力尽了,所以 `insert``remove` 以及所有其他应用程序接口就留给读者练习。
下面是 Cursor 的最终代码,我做对了吗?我只有在写下一章并测试这个怪东西时才能知道!
```rust
pub struct CursorMut<'a, T> {
list: &'a mut LinkedList<T>,
cur: Link<T>,
index: Option<usize>,
}
impl<T> LinkedList<T> {
pub fn cursor_mut(&mut self) -> CursorMut<T> {
CursorMut {
list: self,
cur: None,
index: None,
}
}
}
impl<'a, T> CursorMut<'a, T> {
pub fn index(&self) -> Option<usize> {
self.index
}
pub fn move_next(&mut self) {
if let Some(cur) = self.cur {
unsafe {
// We're on a real element, go to its next (back)
self.cur = (*cur.as_ptr()).back;
if self.cur.is_some() {
*self.index.as_mut().unwrap() += 1;
} else {
// We just walked to the ghost, no more index
self.index = None;
}
}
} else if !self.list.is_empty() {
// We're at the ghost, and there is a real front, so move to it!
self.cur = self.list.front;
self.index = Some(0)
} else {
// We're at the ghost, but that's the only element... do nothing.
}
}
pub fn move_prev(&mut self) {
if let Some(cur) = self.cur {
unsafe {
// We're on a real element, go to its previous (front)
self.cur = (*cur.as_ptr()).front;
if self.cur.is_some() {
*self.index.as_mut().unwrap() -= 1;
} else {
// We just walked to the ghost, no more index
self.index = None;
}
}
} else if !self.list.is_empty() {
// We're at the ghost, and there is a real back, so move to it!
self.cur = self.list.back;
self.index = Some(self.list.len - 1)
} else {
// We're at the ghost, but that's the only element... do nothing.
}
}
pub fn current(&mut self) -> Option<&mut T> {
unsafe {
self.cur.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_next(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).back)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_prev(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).front)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn split_before(&mut self) -> LinkedList<T> {
// We have this:
//
// list.front -> A <-> B <-> C <-> D <- list.back
// ^
// cur
//
//
// And we want to produce this:
//
// list.front -> C <-> D <- list.back
// ^
// cur
//
//
// return.front -> A <-> B <- return.back
//
if let Some(cur) = self.cur {
// We are pointing at a real element, so the list is non-empty.
unsafe {
// Current state
let old_len = self.list.len;
let old_idx = self.index.unwrap();
let prev = (*cur.as_ptr()).front;
// What self will become
let new_len = old_len - old_idx;
let new_front = self.cur;
let new_back = self.list.back;
let new_idx = Some(0);
// What the output will become
let output_len = old_len - new_len;
let output_front = self.list.front;
let output_back = prev;
// Break the links between cur and prev
if let Some(prev) = prev {
(*cur.as_ptr()).front = None;
(*prev.as_ptr()).back = None;
}
// Produce the result:
self.list.len = new_len;
self.list.front = new_front;
self.list.back = new_back;
self.index = new_idx;
LinkedList {
front: output_front,
back: output_back,
len: output_len,
_boo: PhantomData,
}
}
} else {
// We're at the ghost, just replace our list with an empty one.
// No other state needs to be changed.
std::mem::replace(self.list, LinkedList::new())
}
}
pub fn split_after(&mut self) -> LinkedList<T> {
// We have this:
//
// list.front -> A <-> B <-> C <-> D <- list.back
// ^
// cur
//
//
// And we want to produce this:
//
// list.front -> A <-> B <- list.back
// ^
// cur
//
//
// return.front -> C <-> D <- return.back
//
if let Some(cur) = self.cur {
// We are pointing at a real element, so the list is non-empty.
unsafe {
// Current state
let old_len = self.list.len;
let old_idx = self.index.unwrap();
let next = (*cur.as_ptr()).back;
// What self will become
let new_len = old_idx + 1;
let new_back = self.cur;
let new_front = self.list.front;
let new_idx = Some(old_idx);
// What the output will become
let output_len = old_len - new_len;
let output_front = next;
let output_back = self.list.back;
// Break the links between cur and next
if let Some(next) = next {
(*cur.as_ptr()).back = None;
(*next.as_ptr()).front = None;
}
// Produce the result:
self.list.len = new_len;
self.list.front = new_front;
self.list.back = new_back;
self.index = new_idx;
LinkedList {
front: output_front,
back: output_back,
len: output_len,
_boo: PhantomData,
}
}
} else {
// We're at the ghost, just replace our list with an empty one.
// No other state needs to be changed.
std::mem::replace(self.list, LinkedList::new())
}
}
pub fn splice_before(&mut self, mut input: LinkedList<T>) {
// We have this:
//
// input.front -> 1 <-> 2 <- input.back
//
// list.front -> A <-> B <-> C <- list.back
// ^
// cur
//
//
// Becoming this:
//
// list.front -> A <-> 1 <-> 2 <-> B <-> C <- list.back
// ^
// cur
//
unsafe {
// We can either `take` the input's pointers or `mem::forget`
// it. Using `take` is more responsible in case we ever do custom
// allocators or something that also needs to be cleaned up!
if input.is_empty() {
// Input is empty, do nothing.
} else if let Some(cur) = self.cur {
// Both lists are non-empty
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
if let Some(prev) = (*cur.as_ptr()).front {
// General Case, no boundaries, just internal fixups
(*prev.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(prev);
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
} else {
// No prev, we're appending to the front
(*cur.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(cur);
self.list.front = Some(in_front);
}
// Index moves forward by input length
*self.index.as_mut().unwrap() += input.len;
} else if let Some(back) = self.list.back {
// We're on the ghost but non-empty, append to the back
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
(*back.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(back);
self.list.back = Some(in_back);
} else {
// We're empty, become the input, remain on the ghost
std::mem::swap(self.list, &mut input);
}
self.list.len += input.len;
// Not necessary but Polite To Do
input.len = 0;
// Input dropped here
}
}
pub fn splice_after(&mut self, mut input: LinkedList<T>) {
// We have this:
//
// input.front -> 1 <-> 2 <- input.back
//
// list.front -> A <-> B <-> C <- list.back
// ^
// cur
//
//
// Becoming this:
//
// list.front -> A <-> B <-> 1 <-> 2 <-> C <- list.back
// ^
// cur
//
unsafe {
// We can either `take` the input's pointers or `mem::forget`
// it. Using `take` is more responsible in case we ever do custom
// allocators or something that also needs to be cleaned up!
if input.is_empty() {
// Input is empty, do nothing.
} else if let Some(cur) = self.cur {
// Both lists are non-empty
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
if let Some(next) = (*cur.as_ptr()).back {
// General Case, no boundaries, just internal fixups
(*next.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(next);
(*cur.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(cur);
} else {
// No next, we're appending to the back
(*cur.as_ptr()).back = Some(in_front);
(*in_front.as_ptr()).front = Some(cur);
self.list.back = Some(in_back);
}
// Index doesn't change
} else if let Some(front) = self.list.front {
// We're on the ghost but non-empty, append to the front
let in_front = input.front.take().unwrap();
let in_back = input.back.take().unwrap();
(*front.as_ptr()).front = Some(in_back);
(*in_back.as_ptr()).back = Some(front);
self.list.front = Some(in_front);
} else {
// We're empty, become the input, remain on the ghost
std::mem::swap(self.list, &mut input);
}
self.list.len += input.len;
// Not necessary but Polite To Do
input.len = 0;
// Input dropped here
}
}
}
```

@ -0,0 +1,9 @@
# A Production-Quality Unsafe Doubly-Linked Deque
我们终于成功了。我最大的克星:**[std::collections::LinkedList](https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/linked_list.rs),双向链接的 Deque**。
我尝试过但未能击败的那个。
来吧,我将向你展示你需要知道的一切,帮助我一劳永逸地摧毁它--实现一个 **unsafe** 的生产质量双向链接 Deque 所需要知道的一切。
我们将彻底重写我那古老的 Rust 1.0 linked-list crate那个 linked-list 客观上比 std 要好,它从 2015 年开始,就存在 Cursors (游标,后面文章会介绍)而标准库2022年了还没有的东西

@ -0,0 +1,86 @@
# 数据布局
首先,让我们来研究一下敌人的结构。双向链接列表在概念上很简单,但它就是这样欺骗和操纵你的。这是我们反复研究过的同一种链接列表,但链接是双向的。双倍链接,双倍邪恶。
相比于单向(删掉了 Some/None 这类东西以保持简洁):
```text
... -> (A, ptr) -> (B, ptr) -> ...
```
我们需要这个:
```text
... <-> (ptr, A, ptr) <-> (ptr, B, ptr) <-> ...
```
这使你可以从任一方向遍历列表,或使用[cursor(游标)](https://doc.rust-lang.org/std/collections/struct.LinkedList.html#method.cursor_back_mut)来回查找。
为了换取这种灵活性,每个节点必须存储两倍的指针,并且每个操作都必须修复更多的指针。这是一个足够复杂的问题,更容易犯错,所以我们将做大量的测试。
你可能也注意到了,我故意没有画出列表的两端。这正是我们下面的方案中要实现的对方。我们的实现肯定需要两个指针:一个指向列表的起点,另一个指向列表的终点。。
在我看来,有两种值得注意的方法可以做到这一点:“传统节点”和“虚拟节点”。
传统的方法是对堆栈的简单扩展——只需将头部和尾部指针存储在堆栈上:
```text
[ptr, ptr] <-> (ptr, A, ptr) <-> (ptr, B, ptr)
^ ^
+----------------------------------------+
```
这很好,但它有一个缺点:极端情况。现在我们的列表有两个边缘,这意味着极端情况的数量增加了一倍。很容易忘记一个并有一个严重的错误。
虚拟节点方法试图通过在我们的列表中添加一个额外的节点来消除这些极端情况,该节点不包含任何数据,但将两端链接成一个环:
```text
[ptr] -> (ptr, ?DUMMY?, ptr) <-> (ptr, A, ptr) <-> (ptr, B, ptr)
^ ^
+-------------------------------------------------+
```
通过执行此操作,每个节点*始终*具有指向列表中上一个和下一个节点的实际指针。即使你从列表中删除了最后一个元素,你最终也只是拼接了虚拟节点以指向它自己:
```text
[ptr] -> (ptr, ?DUMMY?, ptr)
^ ^
+-------------+
```
一定程度上这非常令人满意和优雅。不幸的是,它有几个实际问题:
问题 1额外的间接和分配尤其是对于必须包含虚拟节点的空列表。可能的解决方案包括
- 在插入某些内容之前不要分配虚拟节点:简单而有效,但它会添加一些我们试图通过使用虚拟指针来避免的极端情况!
- 使用静态的 "copy-on-write" 单例虚拟节点,并采用一些非常巧妙的方案,让 "copy-on-write" 检查捎带上正常检查:看,我真的很想,我真的很喜欢这种东西,但我们不能在这本书中走那条路。如果你想看到那种变态的东西,请阅读 [ThinVec 的源代码](https://docs.rs/thin-vec/0.2.4/src/thin_vec/lib.rs.html#319-325)。
- 将虚拟节点存储在栈上 - 这在没有 C++ 风格的移动构造函数的语言中并不实用。我敢肯定,我们可以在这里用[pinning](https://doc.rust-lang.org/std/pin/index.html)做一些奇怪的事情,但我们不会这样做。
问题 2虚拟节点中存储了什么*值*?当然,如果它是一个整数,那很好,但如果我们存储的是一个满是 Box 的列表呢?我们可能无法初始化这个值!可能的解决方案包括:
- 让每个节点存储`Option<T>`:简单有效,但也臃肿烦人。
- 使每个节点都存储 [`MaybeUninit`](https://doc.rust-lang.org/std/mem/union.MaybeUninit.html)。可怕又烦人。
- 虚拟节点不包含数据字段。这也很诱人,但非常危险和烦人。如果你想看到那种的东西,请阅读 [BTreeMap 的来源](https://doc.rust-lang.org/1.55.0/src/alloc/collections/btree/node.rs.html#49-104)。
对于像 Rust 这样的语言来说,这些虚拟节点方案的问题确实超过了便利性,所以我们将坚持传统的布局。我们将使用与上一章中对不安全队列相同的基本设计:
```rust
#![allow(unused)]
fn main() {
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
}
type Link<T> = *mut Node<T>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
}
```
这还不是一个*真正的*生产质量的布局。不过还不错。我们可以使用一些魔法技巧来告诉 Rust 我们可以做得更好一些。要做到这一点,我们需要 ... 更加深入。

@ -0,0 +1,216 @@
# Send, Sync, and Compile Tests
好吧,其实我们还有一对特征需要考虑,但它们很特别。我们必须对付 Rust 的神圣罗马帝国: unsafe 的 Opt-in Built-out 特征OIBITs Send 和 Sync它们实际上是opt-outbuilt-out3 个中有 1 个已经很不错了!)。
与 Copy 一样这些特征完全没有相关代码只是标记您的类型具有特定属性。Send 表示你的类型可以安全地发送到另一个线程。Sync 表示你的类型可以在线程间安全共享(&Self: Send
关于 LinkedList *covariant(协变的)* 论点在这里同样适用:一般来说,不使用花哨的内部可变技巧的普通集合可以安全地进行 Send 和 Sync。
But I said they're *opt out*. So actually, are we already? How would we know?
让我们在代码中添加一些新的魔法:随机的私有垃圾,除非我们的类型具有我们所期望的属性,否则将无法编译:
```rust
#[allow(dead_code)]
fn assert_properties() {
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}
is_send::<LinkedList<i32>>();
is_sync::<LinkedList<i32>>();
is_send::<IntoIter<i32>>();
is_sync::<IntoIter<i32>>();
is_send::<Iter<i32>>();
is_sync::<Iter<i32>>();
is_send::<IterMut<i32>>();
is_sync::<IterMut<i32>>();
is_send::<Cursor<i32>>();
is_sync::<Cursor<i32>>();
fn linked_list_covariant<'a, T>(x: LinkedList<&'static T>) -> LinkedList<&'a T> { x }
fn iter_covariant<'i, 'a, T>(x: Iter<'i, &'static T>) -> Iter<'i, &'a T> { x }
fn into_iter_covariant<'a, T>(x: IntoIter<&'static T>) -> IntoIter<&'a T> { x }
}
cargo build
Compiling linked-list v0.0.3
error[E0277]: `NonNull<Node<i32>>` cannot be sent between threads safely
--> src\lib.rs:433:5
|
433 | is_send::<LinkedList<i32>>();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ `NonNull<Node<i32>>` cannot be sent between threads safely
|
= help: within `LinkedList<i32>`, the trait `Send` is not implemented for `NonNull<Node<i32>>`
= note: required because it appears within the type `Option<NonNull<Node<i32>>>`
note: required because it appears within the type `LinkedList<i32>`
--> src\lib.rs:8:12
|
8 | pub struct LinkedList<T> {
| ^^^^^^^^^^
note: required by a bound in `is_send`
--> src\lib.rs:430:19
|
430 | fn is_send<T: Send>() {}
| ^^^^ required by this bound in `is_send`
<a million more errors>
```
我骗你说原始指针只有一个安全保护:这是另一个。 `*const``*mut` explicitly opt out of Send and Sync to be safe, so we do *actually* have to opt back in:
```rust
unsafe impl<T: Send> Send for LinkedList<T> {}
unsafe impl<T: Sync> Sync for LinkedList<T> {}
unsafe impl<'a, T: Send> Send for Iter<'a, T> {}
unsafe impl<'a, T: Sync> Sync for Iter<'a, T> {}
unsafe impl<'a, T: Send> Send for IterMut<'a, T> {}
unsafe impl<'a, T: Sync> Sync for IterMut<'a, T> {}
```
请注意,我们必须在这里编写不安全的 impl这些是不安全的特征不安全代码如并发库只能依靠我们正确地实现这些特征由于没有实际代码我们所做的保证只是是的我们在线程间发送或共享确实是安全的
别以为这些都是随便说说的,我可是经过认证的专业人士,我在这里要说:是的,这些都是完全没问题的。请注意,我们并不需要为 IntoIter 实现 Send 和 Sync它只是包含 LinkedList所以会自动生成 Send 和 Sync--我告诉过你它们实际上是 opt out
```text
cargo build
Compiling linked-list v0.0.3
Finished dev [unoptimized + debuginfo] target(s) in 0.18s
```
很好
IterMut 绝对不应该是协变的,因为它 "就像" `&mut T`
用魔术!其实是用 rustdoc好吧我们不一定要使用 rustdoc但这是最有趣的用法。你看如果你写了一个 doccomment 并包含了一个代码块,那么 rustdoc 就会尝试编译并运行它,所以我们可以用它来创建新的匿名 "程序",而这些程序不会影响主程序:
```rust
/// ```
/// use linked_list::IterMut;
///
/// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x }
/// ```
fn iter_mut_invariant() {}
cargo test
...
Doc-tests linked-list
running 1 test
test src\lib.rs - assert_properties::iter_mut_invariant (line 458) ... FAILED
failures:
---- src\lib.rs - assert_properties::iter_mut_invariant (line 458) stdout ----
error[E0308]: mismatched types
--> src\lib.rs:461:86
|
6 | fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x }
| ^ lifetime mismatch
|
= note: expected struct `linked_list::IterMut<'_, &'a T>`
found struct `linked_list::IterMut<'_, &'static T>`
```
好吧我们已经证明了它是不变的但现在我们的测试失败了。不用担心rustdoc 会让你在栅栏上注释 compile_fail说明这是意料之中的
(实际上,我们只证明了它 "不是*covariant(协变的)*",但老实说,如果你能让一个类型 "意外地、错误地*contravariant(逆变的)* ",那么,恭喜你。)
```rust
/// ```compile_fail
/// use linked_list::IterMut;
///
/// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x }
/// ```
fn iter_mut_invariant() {}
cargo test
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.49s
Running unittests src\lib.rs
...
Doc-tests linked-list
running 1 test
test src\lib.rs - assert_properties::iter_mut_invariant (line 458) - compile fail ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
```
是的!我建议在进行测试时不要使用 compile_fail这样你可以看到错误是不是和你预期的一致。例如你忘记了使用 use 关键字,这是错误的,但因为 compile_fail通过了测试。如果不使用compile_fail测试会因为没有使用 use 失败,这不是我们想要的, 我们想要的是:测试因为 `mut` 是*covariant(协变的)*的而失败!
(哦,等等,我们其实可以在 compile_fail 旁边指定我们想要的错误代码,但这只适用于 nightly而且由于上述原因依赖它是个坏主意。在 not-nightly 版本运行时,它将被默默忽略)。
```rust
/// ```compile_fail,E0308
/// use linked_list::IterMut;
///
/// fn iter_mut_covariant<'i, 'a, T>(x: IterMut<'i, &'static T>) -> IterMut<'i, &'a T> { x }
/// ```
fn iter_mut_invariant() {}
```
......还有,你注意到我们实际上把 IterMut 变成*invariant(不变的)*的那部分了吗?这很容易被忽略,因为我 "只是 "复制粘贴了 Iter 并把它放在了最后。这是最后一行:
```rust
pub struct IterMut<'a, T> {
front: Link<T>,
back: Link<T>,
len: usize,
_boo: PhantomData<&'a mut T>,
}
```
我们试着去掉 PhantomData:
```text
cargo build
Compiling linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list)
error[E0392]: parameter `'a` is never used
--> src\lib.rs:30:20
|
30 | pub struct IterMut<'a, T> {
| ^^ unused parameter
|
= help: consider removing `'a`, referring to it in a field, or using a marker such as `PhantomData`
```
哈!编译器在背后支持我们,提示我们未使用的 lifetime。让我们试着用一个错误的例子来代替
```rust
_boo: PhantomData<&'a T>,
cargo build
Compiling linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
```
它可以构建!我们的测试可以发现问题吗?
```text
cargo test
...
Doc-tests linked-list
running 1 test
test src\lib.rs - assert_properties::iter_mut_invariant (line 458) - compile fail ... FAILED
failures:
---- src\lib.rs - assert_properties::iter_mut_invariant (line 458) stdout ----
Test compiled successfully, but it's marked `compile_fail`.
failures:
src\lib.rs - assert_properties::iter_mut_invariant (line 458)
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
```
Eyyy!!.!这个系统真管用!我喜欢那些能真正完成任务的测试,这样我就不必为那些若隐若现的错误而感到恐惧了!

@ -0,0 +1,426 @@
# Testing Cursors
是时候找出我在上一节中犯了多少令人尴尬的错误了!
哦,天哪,我们的 API 既不像标准版,也不像旧版。好吧,那我打算从这两个地方拼凑一些东西吧。是的,让我们 "借用 " 标准版中的这些测试:
```rust
#[test]
fn test_cursor_move_peek() {
let mut m: LinkedList<u32> = LinkedList::new();
m.extend([1, 2, 3, 4, 5, 6]);
let mut cursor = m.cursor_mut();
cursor.move_next();
assert_eq!(cursor.current(), Some(&mut 1));
assert_eq!(cursor.peek_next(), Some(&mut 2));
assert_eq!(cursor.peek_prev(), None);
assert_eq!(cursor.index(), Some(0));
cursor.move_prev();
assert_eq!(cursor.current(), None);
assert_eq!(cursor.peek_next(), Some(&mut 1));
assert_eq!(cursor.peek_prev(), Some(&mut 6));
assert_eq!(cursor.index(), None);
cursor.move_next();
cursor.move_next();
assert_eq!(cursor.current(), Some(&mut 2));
assert_eq!(cursor.peek_next(), Some(&mut 3));
assert_eq!(cursor.peek_prev(), Some(&mut 1));
assert_eq!(cursor.index(), Some(1));
let mut cursor = m.cursor_mut();
cursor.move_prev();
assert_eq!(cursor.current(), Some(&mut 6));
assert_eq!(cursor.peek_next(), None);
assert_eq!(cursor.peek_prev(), Some(&mut 5));
assert_eq!(cursor.index(), Some(5));
cursor.move_next();
assert_eq!(cursor.current(), None);
assert_eq!(cursor.peek_next(), Some(&mut 1));
assert_eq!(cursor.peek_prev(), Some(&mut 6));
assert_eq!(cursor.index(), None);
cursor.move_prev();
cursor.move_prev();
assert_eq!(cursor.current(), Some(&mut 5));
assert_eq!(cursor.peek_next(), Some(&mut 6));
assert_eq!(cursor.peek_prev(), Some(&mut 4));
assert_eq!(cursor.index(), Some(4));
}
#[test]
fn test_cursor_mut_insert() {
let mut m: LinkedList<u32> = LinkedList::new();
m.extend([1, 2, 3, 4, 5, 6]);
let mut cursor = m.cursor_mut();
cursor.move_next();
cursor.splice_before(Some(7).into_iter().collect());
cursor.splice_after(Some(8).into_iter().collect());
// check_links(&m);
assert_eq!(m.iter().cloned().collect::<Vec<_>>(), &[7, 1, 8, 2, 3, 4, 5, 6]);
let mut cursor = m.cursor_mut();
cursor.move_next();
cursor.move_prev();
cursor.splice_before(Some(9).into_iter().collect());
cursor.splice_after(Some(10).into_iter().collect());
check_links(&m);
assert_eq!(m.iter().cloned().collect::<Vec<_>>(), &[10, 7, 1, 8, 2, 3, 4, 5, 6, 9]);
/* remove_current not impl'd
let mut cursor = m.cursor_mut();
cursor.move_next();
cursor.move_prev();
assert_eq!(cursor.remove_current(), None);
cursor.move_next();
cursor.move_next();
assert_eq!(cursor.remove_current(), Some(7));
cursor.move_prev();
cursor.move_prev();
cursor.move_prev();
assert_eq!(cursor.remove_current(), Some(9));
cursor.move_next();
assert_eq!(cursor.remove_current(), Some(10));
check_links(&m);
assert_eq!(m.iter().cloned().collect::<Vec<_>>(), &[1, 8, 2, 3, 4, 5, 6]);
*/
let mut m: LinkedList<u32> = LinkedList::new();
m.extend([1, 8, 2, 3, 4, 5, 6]);
let mut cursor = m.cursor_mut();
cursor.move_next();
let mut p: LinkedList<u32> = LinkedList::new();
p.extend([100, 101, 102, 103]);
let mut q: LinkedList<u32> = LinkedList::new();
q.extend([200, 201, 202, 203]);
cursor.splice_after(p);
cursor.splice_before(q);
check_links(&m);
assert_eq!(
m.iter().cloned().collect::<Vec<_>>(),
&[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6]
);
let mut cursor = m.cursor_mut();
cursor.move_next();
cursor.move_prev();
let tmp = cursor.split_before();
assert_eq!(m.into_iter().collect::<Vec<_>>(), &[]);
m = tmp;
let mut cursor = m.cursor_mut();
cursor.move_next();
cursor.move_next();
cursor.move_next();
cursor.move_next();
cursor.move_next();
cursor.move_next();
cursor.move_next();
let tmp = cursor.split_after();
assert_eq!(tmp.into_iter().collect::<Vec<_>>(), &[102, 103, 8, 2, 3, 4, 5, 6]);
check_links(&m);
assert_eq!(m.iter().cloned().collect::<Vec<_>>(), &[200, 201, 202, 203, 1, 100, 101]);
}
fn check_links<T>(_list: &LinkedList<T>) {
// would be good to do this!
}
```
见证奇迹的时候!
```text
cargo test
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 1.03s
Running unittests src\lib.rs
running 14 tests
test test::test_basic_front ... ok
test test::test_basic ... ok
test test::test_debug ... ok
test test::test_iterator_mut_double_end ... ok
test test::test_ord ... ok
test test::test_cursor_move_peek ... FAILED
test test::test_cursor_mut_insert ... FAILED
test test::test_iterator ... ok
test test::test_mut_iter ... ok
test test::test_eq ... ok
test test::test_rev_iter ... ok
test test::test_iterator_double_end ... ok
test test::test_hashmap ... ok
test test::test_ord_nan ... ok
failures:
---- test::test_cursor_move_peek stdout ----
thread 'test::test_cursor_move_peek' panicked at 'assertion failed: `(left == right)`
left: `None`,
right: `Some(1)`', src\lib.rs:1079:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
---- test::test_cursor_mut_insert stdout ----
thread 'test::test_cursor_mut_insert' panicked at 'assertion failed: `(left == right)`
left: `[200, 201, 202, 203, 10, 100, 101, 102, 103, 7, 1, 8, 2, 3, 4, 5, 6, 9]`,
right: `[200, 201, 202, 203, 1, 100, 101, 102, 103, 8, 2, 3, 4, 5, 6]`', src\lib.rs:1153:9
failures:
test::test_cursor_move_peek
test::test_cursor_mut_insert
test result: FAILED. 12 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```
我得承认,我在这里有些自负,希望自己能成功。这就是我们写测试的原因(但也许我只是在移植测试时做得不好?)
第一次失败是什么?
```rust
let mut m: LinkedList<u32> = LinkedList::new();
m.extend([1, 2, 3, 4, 5, 6]);
let mut cursor = m.cursor_mut();
cursor.move_next();
assert_eq!(cursor.current(), Some(&mut 1));
assert_eq!(cursor.peek_next(), Some(&mut 2));
assert_eq!(cursor.peek_prev(), None);
assert_eq!(cursor.index(), Some(0));
cursor.move_prev();
assert_eq!(cursor.current(), None);
assert_eq!(cursor.peek_next(), Some(&mut 1)); // DIES HERE
```
```rust
pub fn peek_next(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).back)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
```
就是这错了。如果 `self.cur` 是 None, 我们不应该就这样放弃,我们还需要检查 self.list.front因为我们在幽灵节点上因此我们只需在链中添加一个 or_else
```rust
pub fn peek_next(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).back)
.or_else(|| self.list.front)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_prev(&mut self) -> Option<&mut T> {
unsafe {
self.cur
.and_then(|node| (*node.as_ptr()).front)
.or_else(|| self.list.back)
.map(|node| &mut (*node.as_ptr()).elem)
}
}
```
这样就修复好了吗?
```text
---- test::test_cursor_move_peek stdout ----
thread 'test::test_cursor_move_peek' panicked at 'assertion failed: `(left == right)`
left: `Some(6)`,
right: `None`', src\lib.rs:1078:9
```
又错了。好吧,显然这比我想象的要难得多。盲目地把这些情况串联起来简直是一场灾难,让我们对幽灵节点与非幽灵节点的情况做不同的判断吧:
```rust
pub fn peek_next(&mut self) -> Option<&mut T> {
unsafe {
let next = if let Some(cur) = self.cur {
// Normal case, try to follow the cur node's back pointer
(*cur.as_ptr()).back
} else {
// Ghost case, try to use the list's front pointer
self.list.front
};
// Yield the element if the next node exists
next.map(|node| &mut (*node.as_ptr()).elem)
}
}
pub fn peek_prev(&mut self) -> Option<&mut T> {
unsafe {
let prev = if let Some(cur) = self.cur {
// Normal case, try to follow the cur node's front pointer
(*cur.as_ptr()).front
} else {
// Ghost case, try to use the list's back pointer
self.list.back
};
// Yield the element if the prev node exists
prev.map(|node| &mut (*node.as_ptr()).elem)
}
}
```
我对这一次充满信心!
```rust
cargo test
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.70s
Running unittests src\lib.rs
running 14 tests
test test::test_basic_front ... ok
test test::test_basic ... ok
test test::test_cursor_move_peek ... ok
test test::test_eq ... ok
test test::test_cursor_mut_insert ... ok
test test::test_iterator ... ok
test test::test_iterator_double_end ... ok
test test::test_ord_nan ... ok
test test::test_mut_iter ... ok
test test::test_hashmap ... ok
test test::test_debug ... ok
test test::test_ord ... ok
test test::test_iterator_mut_double_end ... ok
test test::test_rev_iter ... ok
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests linked-list
running 1 test
test src\lib.rs - assert_properties::iter_mut_invariant (line 803) - compile fail ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
```
嘿嘿,看看这个......好吧,现在我开始疑神疑鬼了。让我们正确填写 check_links并在 miri 下进行测试:
```rust
fn check_links<T: Eq + std::fmt::Debug>(list: &LinkedList<T>) {
let from_front: Vec<_> = list.iter().collect();
let from_back: Vec<_> = list.iter().rev().collect();
let re_reved: Vec<_> = from_back.into_iter().rev().collect();
assert_eq!(from_front, re_reved);
}
```
```text
$env:MIRIFLAGS="-Zmiri-tag-raw-pointers"
cargo miri test
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.25s
Running unittests src\lib.rs
running 14 tests
test test::test_basic ... ok
test test::test_basic_front ... ok
test test::test_cursor_move_peek ... ok
test test::test_cursor_mut_insert ... ok
test test::test_debug ... ok
test test::test_eq ... ok
test test::test_hashmap ... ok
test test::test_iterator ... ok
test test::test_iterator_double_end ... ok
test test::test_iterator_mut_double_end ... ok
test test::test_mut_iter ... ok
test test::test_ord ... ok
test test::test_ord_nan ... ok
test test::test_rev_iter ... ok
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests linked-list
running 1 test
test src\lib.rs - assert_properties::iter_mut_invariant (line 803) - compile fail ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.10s
```
完成。
我们成功了 我们做出了一个具有生产质量的 LinkedList其功能与 std 中的 LinkedList 基本相同。我们是否在这里或那里缺少了一些小的便利方法?当然有。我会把它们添加到最终发布的版本中吗?也许会!
但是,我已经非常累了。
所以。我们赢了
等等 我们正在生产质量。好吧,最后一个步骤: clippy。
```text
cargo clippy
cargo clippy
Checking linked-list v0.0.3 (C:\Users\ninte\dev\contain\linked-list)
warning: redundant pattern matching, consider using `is_some()`
--> src\lib.rs:189:19
|
189 | while let Some(_) = self.pop_front() { }
| ----------^^^^^^^------------------- help: try this: `while self.pop_front().is_some()`
|
= note: `#[warn(clippy::redundant_pattern_matching)]` on by default
= note: this will change drop order of the result, as well as all temporaries
= note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
warning: method `into_iter` can be confused for the standard trait method `std::iter::IntoIterator::into_iter`
--> src\lib.rs:210:5
|
210 | / pub fn into_iter(self) -> IntoIter<T> {
211 | | IntoIter {
212 | | list: self
213 | | }
214 | | }
| |_____^
|
= note: `#[warn(clippy::should_implement_trait)]` on by default
= help: consider implementing the trait `std::iter::IntoIterator` or choosing a less ambiguous method name
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#should_implement_trait
warning: redundant pattern matching, consider using `is_some()`
--> src\lib.rs:228:19
|
228 | while let Some(_) = self.pop_front() { }
| ----------^^^^^^^------------------- help: try this: `while self.pop_front().is_some()`
|
= note: this will change drop order of the result, as well as all temporaries
= note: add `#[allow(clippy::redundant_pattern_matching)]` if this is important
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#redundant_pattern_matching
warning: re-implementing `PartialEq::ne` is unnecessary
--> src\lib.rs:275:5
|
275 | / fn ne(&self, other: &Self) -> bool {
276 | | self.len() != other.len() || self.iter().ne(other)
277 | | }
| |_____^
|
= note: `#[warn(clippy::partialeq_ne_impl)]` on by default
= help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
warning: `linked-list` (lib) generated 4 warnings
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
```
好的 clippy, 按照你的要求修改。
再来一次:
```text
cargo clippy
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
```
太棒了,称为生产品质的最后一件事: fmt.
```text
cargo fmt
```
**我们现在终于真正的完成啦!!!!!!!!!!!!!!!!!!!!!**

@ -0,0 +1,328 @@
# Testing
好吧,我推迟了一段时间测试,因为我们都知道,我们现在是 Rust 的主人,不会再犯错了!另外,这是对一个旧 crate 的重写,所以我已经有了所有的测试。你已经看过很多测试了。它们就在这里:
```rust
#[cfg(test)]
mod test {
use super::LinkedList;
fn generate_test() -> LinkedList<i32> {
list_from(&[0, 1, 2, 3, 4, 5, 6])
}
fn list_from<T: Clone>(v: &[T]) -> LinkedList<T> {
v.iter().map(|x| (*x).clone()).collect()
}
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Try to break an empty list
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Try to break a one item list
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Mess around
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
#[test]
fn test_basic() {
let mut m = LinkedList::new();
assert_eq!(m.pop_front(), None);
assert_eq!(m.pop_back(), None);
assert_eq!(m.pop_front(), None);
m.push_front(1);
assert_eq!(m.pop_front(), Some(1));
m.push_back(2);
m.push_back(3);
assert_eq!(m.len(), 2);
assert_eq!(m.pop_front(), Some(2));
assert_eq!(m.pop_front(), Some(3));
assert_eq!(m.len(), 0);
assert_eq!(m.pop_front(), None);
m.push_back(1);
m.push_back(3);
m.push_back(5);
m.push_back(7);
assert_eq!(m.pop_front(), Some(1));
let mut n = LinkedList::new();
n.push_front(2);
n.push_front(3);
{
assert_eq!(n.front().unwrap(), &3);
let x = n.front_mut().unwrap();
assert_eq!(*x, 3);
*x = 0;
}
{
assert_eq!(n.back().unwrap(), &2);
let y = n.back_mut().unwrap();
assert_eq!(*y, 2);
*y = 1;
}
assert_eq!(n.pop_front(), Some(0));
assert_eq!(n.pop_front(), Some(1));
}
#[test]
fn test_iterator() {
let m = generate_test();
for (i, elt) in m.iter().enumerate() {
assert_eq!(i as i32, *elt);
}
let mut n = LinkedList::new();
assert_eq!(n.iter().next(), None);
n.push_front(4);
let mut it = n.iter();
assert_eq!(it.size_hint(), (1, Some(1)));
assert_eq!(it.next().unwrap(), &4);
assert_eq!(it.size_hint(), (0, Some(0)));
assert_eq!(it.next(), None);
}
#[test]
fn test_iterator_double_end() {
let mut n = LinkedList::new();
assert_eq!(n.iter().next(), None);
n.push_front(4);
n.push_front(5);
n.push_front(6);
let mut it = n.iter();
assert_eq!(it.size_hint(), (3, Some(3)));
assert_eq!(it.next().unwrap(), &6);
assert_eq!(it.size_hint(), (2, Some(2)));
assert_eq!(it.next_back().unwrap(), &4);
assert_eq!(it.size_hint(), (1, Some(1)));
assert_eq!(it.next_back().unwrap(), &5);
assert_eq!(it.next_back(), None);
assert_eq!(it.next(), None);
}
#[test]
fn test_rev_iter() {
let m = generate_test();
for (i, elt) in m.iter().rev().enumerate() {
assert_eq!(6 - i as i32, *elt);
}
let mut n = LinkedList::new();
assert_eq!(n.iter().rev().next(), None);
n.push_front(4);
let mut it = n.iter().rev();
assert_eq!(it.size_hint(), (1, Some(1)));
assert_eq!(it.next().unwrap(), &4);
assert_eq!(it.size_hint(), (0, Some(0)));
assert_eq!(it.next(), None);
}
#[test]
fn test_mut_iter() {
let mut m = generate_test();
let mut len = m.len();
for (i, elt) in m.iter_mut().enumerate() {
assert_eq!(i as i32, *elt);
len -= 1;
}
assert_eq!(len, 0);
let mut n = LinkedList::new();
assert!(n.iter_mut().next().is_none());
n.push_front(4);
n.push_back(5);
let mut it = n.iter_mut();
assert_eq!(it.size_hint(), (2, Some(2)));
assert!(it.next().is_some());
assert!(it.next().is_some());
assert_eq!(it.size_hint(), (0, Some(0)));
assert!(it.next().is_none());
}
#[test]
fn test_iterator_mut_double_end() {
let mut n = LinkedList::new();
assert!(n.iter_mut().next_back().is_none());
n.push_front(4);
n.push_front(5);
n.push_front(6);
let mut it = n.iter_mut();
assert_eq!(it.size_hint(), (3, Some(3)));
assert_eq!(*it.next().unwrap(), 6);
assert_eq!(it.size_hint(), (2, Some(2)));
assert_eq!(*it.next_back().unwrap(), 4);
assert_eq!(it.size_hint(), (1, Some(1)));
assert_eq!(*it.next_back().unwrap(), 5);
assert!(it.next_back().is_none());
assert!(it.next().is_none());
}
#[test]
fn test_eq() {
let mut n: LinkedList<u8> = list_from(&[]);
let mut m = list_from(&[]);
assert!(n == m);
n.push_front(1);
assert!(n != m);
m.push_back(1);
assert!(n == m);
let n = list_from(&[2, 3, 4]);
let m = list_from(&[1, 2, 3]);
assert!(n != m);
}
#[test]
fn test_ord() {
let n = list_from(&[]);
let m = list_from(&[1, 2, 3]);
assert!(n < m);
assert!(m > n);
assert!(n <= n);
assert!(n >= n);
}
#[test]
fn test_ord_nan() {
let nan = 0.0f64 / 0.0;
let n = list_from(&[nan]);
let m = list_from(&[nan]);
assert!(!(n < m));
assert!(!(n > m));
assert!(!(n <= m));
assert!(!(n >= m));
let n = list_from(&[nan]);
let one = list_from(&[1.0f64]);
assert!(!(n < one));
assert!(!(n > one));
assert!(!(n <= one));
assert!(!(n >= one));
let u = list_from(&[1.0f64, 2.0, nan]);
let v = list_from(&[1.0f64, 2.0, 3.0]);
assert!(!(u < v));
assert!(!(u > v));
assert!(!(u <= v));
assert!(!(u >= v));
let s = list_from(&[1.0f64, 2.0, 4.0, 2.0]);
let t = list_from(&[1.0f64, 2.0, 3.0, 2.0]);
assert!(!(s < t));
assert!(s > one);
assert!(!(s <= one));
assert!(s >= one);
}
#[test]
fn test_debug() {
let list: LinkedList<i32> = (0..10).collect();
assert_eq!(format!("{:?}", list), "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]");
let list: LinkedList<&str> = vec!["just", "one", "test", "more"]
.iter().copied()
.collect();
assert_eq!(format!("{:?}", list), r#"["just", "one", "test", "more"]"#);
}
#[test]
fn test_hashmap() {
// Check that HashMap works with this as a key
let list1: LinkedList<i32> = (0..10).collect();
let list2: LinkedList<i32> = (1..11).collect();
let mut map = std::collections::HashMap::new();
assert_eq!(map.insert(list1.clone(), "list1"), None);
assert_eq!(map.insert(list2.clone(), "list2"), None);
assert_eq!(map.len(), 2);
assert_eq!(map.get(&list1), Some(&"list1"));
assert_eq!(map.get(&list2), Some(&"list2"));
assert_eq!(map.remove(&list1), Some("list1"));
assert_eq!(map.remove(&list2), Some("list2"));
assert!(map.is_empty());
}
}
```
现在是关键时刻:
```text
cargo test
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src\lib.rs
running 12 tests
test test::test_basic ... ok
test test::test_basic_front ... ok
test test::test_eq ... ok
test test::test_iterator ... ok
test test::test_iterator_mut_double_end ... ok
test test::test_ord_nan ... ok
test test::test_iterator_double_end ... ok
test test::test_mut_iter ... ok
test test::test_rev_iter ... ok
test test::test_hashmap ... ok
test test::test_ord ... ok
test test::test_debug ... ok
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
$env:MIRIFLAGS="-Zmiri-tag-raw-pointers"
cargo miri test
Compiling linked-list v0.0.3
Finished test [unoptimized + debuginfo] target(s) in 0.35s
Running unittests src\lib.rs
running 12 tests
test test::test_basic ... ok
test test::test_basic_front ... ok
test test::test_debug ... ok
test test::test_eq ... ok
test test::test_hashmap ... ok
test test::test_iterator ... ok
test test::test_iterator_double_end ... ok
test test::test_iterator_mut_double_end ... ok
test test::test_mut_iter ... ok
test test::test_ord ... ok
test test::test_ord_nan ... ok
test test::test_rev_iter ... ok
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
😭
我们做到了,我们真的没有搞砸。这不是小把戏!我们所有的练习和训练终于值得了!我们终于写出了好代码
现在,我们可以回到 "有趣的事情 "上来了!

@ -0,0 +1,186 @@
# Variance and PhantomData
如果现在不做,等以后再修,会很麻烦,所以我们现在要做的是硬核布局。
建造 Rust collections 时,有这五个可怕的难题:
1. [Variance](https://doc.rust-lang.org/nightly/nomicon/subtyping.html)
2. [Drop Check](https://doc.rust-lang.org/nightly/nomicon/dropck.html)
3. [NonNull Optimizations](https://doc.rust-lang.org/nightly/std/ptr/struct.NonNull.html)
4. [The isize::MAX Allocation Rule](https://doc.rust-lang.org/nightly/nomicon/vec/vec-alloc.html)
5. [Zero-Sized Types](https://doc.rust-lang.org/nightly/nomicon/vec/vec-zsts.html)
幸好后面2个对我们来说都不是问题。
我们可以把第三个问题变成我们的问题,但这带来的麻烦比它的价值更多。
第二个问题是我以前一直坚持认为非常重要的std 也会乱用它,但默认值是安全的,而且你需要非常努力才能注意到默认值的限制,所以不用担心这个问题。
所以只剩下了 Variance(型变)。
Rust 有子类型了。通常,`&'big T` 是 `&'small T` 的子类型。因为如果某些代码需要在程序的某个特定区域存活的引用,那么通常完全可以给它一个存在*时间更长的*引用。直觉上这是正确的,对吧?
为什么这很重要?想象一下,一些代码采用两个具有相同类型的值:
```rust
fn take_two<T>(_val1: T, _val2: T) { }
```
这是一些非常无聊的代码,并且我们期望它能够很好地与 T=&u32 一起使用,对吧?
```rust
fn two_refs<'big: 'small, 'small>(
big: &'big u32,
small: &'small u32,
) {
take_two(big, small);
}
fn take_two<T>(_val1: T, _val2: T) { }
```
是的,编译得很好!
现在让我们找点乐子,把它包起来:`std::cell::Cell`
```rust
use std::cell::Cell;
fn two_refs<'big: 'small, 'small>(
// NOTE: these two lines changed
big: Cell<&'big u32>,
small: Cell<&'small u32>,
) {
take_two(big, small);
}
fn take_two<T>(_val1: T, _val2: T) { }
error[E0623]: lifetime mismatch
--> src/main.rs:7:19
|
4 | big: Cell<&'big u32>,
| ---------
5 | small: Cell<&'small u32>,
| ----------- these two types are declared with different lifetimes...
6 | ) {
7 | take_two(big, small);
| ^^^^^ ...but data from `small` flows into `big` here
```
哼???我们没有碰过生命周期,为什么编译器现在生气了!?
啊,好吧,生命周期的“子类型”必须非常简单,所以如果你将引用包装在任何东西中,它就会被破坏,看看 Vec
```rust
fn two_refs<'big: 'small, 'small>(
big: Vec<&'big u32>,
small: Vec<&'small u32>,
) {
take_two(big, small);
}
fn take_two<T>(_val1: T, _val2: T) { }
Finished dev [unoptimized + debuginfo] target(s) in 1.07s
Running `target/debug/playground`
```
看到它没有编译成功 ——等等???Vec是魔术??????
是的。这种魔力就是✨*Variance*✨。
如果您想要所有细节,请阅读 [nomicon 关于子类型的章节](https://doc.rust-lang.org/nightly/nomicon/subtyping.html),但基本上子类型*并不总是*安全的。特别是,当涉及可变引用时,它就更不安全了,。因为你可能会使用诸如`mem::swap`的东西,突然哎呀,悬空指针!
可变引用是 *invariant(不变的)*,这意味着它们会阻止对泛型参数子类型化。因此,为了安全起见, `&mut T` 在 T 上是不变的,并且 `Cell<T>` 在 T 上也是不变的(因为内部可变性),因为 `&Cell<T>` 本质上就像 `&mut T`
几乎所有不是 *invariant* 的东西都是 *covariant(协变的)* ,这意味着子类型可以正常工作(也有 *contravariant(逆变的)* 的类型使子类型倒退,但它们真的很少见,没有人喜欢它们,所以我不会再提到它们)。
集合通常包含指向其数据的可变指针,因此你可能希望它们也是不变的,但事实上,它们并不需要不变!由于 Rust 的所有权系统,`Vec<T>` 在语义上等同于 `T`,这意味着它可以安全地保持*covariant(协变的)*
不幸的的是,下面的定义是 *invariant(不变的)*:
```rust
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
}
type Link<T> = *mut Node<T>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
```
所以我们的类型定义中哪里惹 Rust 编译器不高兴了? `*mut`
Rust 中的裸指针其实就是让你可以做任何事情,但它们只有一个安全特性:因为大多数人都不知道 Rust 中还有 *Variance(型变)* 和子类型,而错误地使用 *covariant(协变的)* 会非常危险,所以 `*mut T` 是*invariant(不变的)*,因为它很有可能被 "作为" `&mut T` 使用。
作为一个花了大量时间在 Rust 中编写集合的人,这让我感到厌烦。这就是为什么我在制作 [std::ptr::NonNull](https://doc.rust-lang.org/std/ptr/struct.NonNull.html), 时添加了这个小魔法:
> 与 *mut T 不同NonNull<T> 在 T 上是 *covariant(协变的)*。这使得使用 NonNull<T> 构建*covariant(协变的)*类型成为可能,但如果在不应该是 *covariant(协变的)* 的地方中使用,则会带来不健全的风险。
这是一个围绕着 `*mut T` 构建的类型。真的是魔法吗?让我们来看一下:
```rust
pub struct NonNull<T> {
pointer: *const T,
}
impl<T> NonNull<T> {
pub unsafe fn new_unchecked(ptr: *mut T) -> Self {
// SAFETY: the caller must guarantee that `ptr` is non-null.
unsafe { NonNull { pointer: ptr as *const T } }
}
}
```
这里没有魔法NonNull 只是滥用了 *const T 是 *covariant(协变的)* 这一事实,并将其存储起来。这就是 Rust 中集合的协变方式!这可真是惨不忍睹!所以我为你做了这个 Good Pointer Type !不客气好好享受子类型吧
解决你所有问题的办法就是使用 NonNull然后如果你想再次使用可空指针就使用 Option<NonNull<T>>。我们真的要这么做吗?
是的!这很糟糕,但我们要做的是生产级的链表,所以我们要吃尽千辛万苦(我们可以直接使用裸*const T然后在任何地方都进行转换但我真的想看看这样做有多痛苦......为了人体工程学科学)。
下面就是我们最终的类型定义:
```rust
use std::ptr::NonNull;
// !!!This changed!!!
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
}
type Link<T> = Option<NonNull<Node<T>>>;
struct Node<T> {
front: Link<T>,
back: Link<T>,
elem: T,
}
```
...等等,不,最后一件事。每当你使用裸指针时,你都应该添加一个 Ghost 来保护你的指针:
```rust
use std::marker::PhantomData;
pub struct LinkedList<T> {
front: Link<T>,
back: Link<T>,
len: usize,
/// We semantically store values of T by-value.
_boo: PhantomData<T>,
}
```
在这种情况下,我认为我们*实际上*不需要 [PhantomData](https://doc.rust-lang.org/std/marker/struct.PhantomData.html),但每当你使用 NonNull或一般的裸指针为了安全起见你都应该始终添加它并向编译器和其他人清楚地表明你的想法你在做什么。
PhantomData 是我们给编译器提供一个额外的 "示例 "字段的方法,这个字段在概念上存在于你的类型中,但由于各种原因(间接、类型擦除......)并不存在。在本例中,我们使用 NonNull 是因为我们声称我们的类型 "好像 "存储了一个值 T所以我们添加了一个 PhantomData 来明确这一点。
...好吧,我们现在已经完成了布局!进入实际的基本功能!
Loading…
Cancel
Save