Merge branch 'sunface:main' into main

pull/1346/head
Scott Rhodes 7 months ago committed by GitHub
commit 4948ac47cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -47,24 +47,17 @@
<table>
<tr>
<td align="center">
<a href="https://github.com/sunface">
<img src="https://avatars.githubusercontent.com/u/7036754?v=4?s=100" width="160px" alt=""/>
<a href="https://github.com/EluvK">
<img src="https://avatars.githubusercontent.com/u/36977935?v=4" width="160px" alt=""/>
<br />
<sub><b>Sunface 🥇</b></sub>
<sub><b>EluvK</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AllanDowney">
<img src="https://avatars.githubusercontent.com/u/82752697?v=4?s=100" width="160px" alt=""/>
<br />
<sub><b>AllanDowney 🥈</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/EluvK">
<img src="https://avatars.githubusercontent.com/u/36977935?v=4" width="160px" alt=""/>
<br />
<sub><b>EluvK 🥉</b></sub>
<sub><b>AllanDowney</b></sub>
</a>
</td>
</tr>
@ -76,6 +69,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 +97,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>
@ -141,7 +134,10 @@
## 社区 & 读者交流
- 知乎: [孙飞 Sunface](https://www.zhihu.com/people/iSunface)
- QQ 群 `1009730433`,用于日常技术交流
- RustCn 微信交流 2 群:<img src="https://github.com/sunface/rust-course/assets/7036754/a84ec7e5-30b1-48da-9352-95503aa61a8f" width="200" />
- 微信公众号: 扫描下面的二维码关注公众号 `Rust语言中文网`
<img src="https://github.com/sunface/rust-course/blob/main/assets/studyrust公众号.png?raw=true" />

@ -6,18 +6,14 @@
<!-- [快速查询入口](index-list.md) 暂时屏蔽-->
[社区和锈书](community.md)
---
[Datav: 可编程的数据可视化平台和可观测性平台](some-thoughts.md)
[Xobserve: 一切皆可观测](some-thoughts.md)
[BeatAI: 工程师 AI 入门圣经](beat-ai.md)
<!-- [一本生锈的书](rusty-book.md) -->
<!-- [Rust 语言周刊](rust-weekly.md) -->
<!-- [Rust 翻译计划( 代号 Rustt )](rustt.md) -->
# Rust 语言基础学习
---
@ -29,7 +25,6 @@
- [不仅仅是 Hello world](first-try/hello-world.md)
- [下载依赖太慢了?](first-try/slowly-downloading.md)
- [Rust 基础入门](basic/intro.md)
- [变量绑定与解构](basic/variable.md)
@ -135,7 +130,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 +241,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 +318,6 @@
- [编译器优化 todo](profiling/compiler/optimization/intro.md)
- [Option 枚举 todo](profiling/compiler/optimization/option.md)
<!-- - [标准库解析 todo](std/intro.md)
- [标准库使用最佳实践 todo](std/search.md)
@ -378,3 +381,6 @@
- [1.72](appendix/rust-versions/1.72.md)
- [1.73](appendix/rust-versions/1.73.md)
- [1.74](appendix/rust-versions/1.74.md)
- [1.75](appendix/rust-versions/1.75.md)
- [1.76](appendix/rust-versions/1.76.md)
- [1.77](appendix/rust-versions/1.77.md)

@ -43,24 +43,17 @@ Rust 语言真的好:连续八年成为全世界最受欢迎的语言、没有
<table>
<tr>
<td align="center">
<a href="https://github.com/sunface">
<img src="https://avatars.githubusercontent.com/u/7036754?v=4?s=100" width="160px" alt=""/>
<a href="https://github.com/EluvK">
<img src="https://avatars.githubusercontent.com/u/36977935?v=4" width="160px" alt=""/>
<br />
<sub><b>Sunface 🥇</b></sub>
<sub><b>EluvK</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AllanDowney">
<img src="https://avatars.githubusercontent.com/u/82752697?v=4?s=100" width="160px" alt=""/>
<br />
<sub><b>AllanDowney 🥈</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/EluvK">
<img src="https://avatars.githubusercontent.com/u/36977935?v=4" width="160px" alt=""/>
<br />
<sub><b>EluvK 🥉</b></sub>
<sub><b>AllanDowney</b></sub>
</a>
</td>
</tr>

@ -343,7 +343,7 @@ impl Worker {
在上面的代码中, `thread::spawn(|| {})` 还没有给予实质性的内容,现在一起来完善下。
首先 `Worker` 结构体需要从线程池 `TreadPool` 的队列中获取待执行的代码,对于这类场景,消息传递非常适合:我们将使用消息通道( channel )作为任务队列。
首先 `Worker` 结构体需要从线程池 `ThreadPool` 的队列中获取待执行的代码,对于这类场景,消息传递非常适合:我们将使用消息通道( channel )作为任务队列。
```rust
use std::{sync::mpsc, thread};

@ -136,6 +136,7 @@ Request: [
```text
Method Request-URI HTTP-Version
headers CRLF
message-body
```
@ -152,6 +153,7 @@ message-body
```text
HTTP-Version Status-Code Reason-Phrase CRLF
headers CRLF
message-body
```
@ -227,7 +229,7 @@ fn handle_connection(mut stream: TcpStream) {
let length = contents.len();
let response =
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n\n{contents}");
stream.write_all(response.as_bytes()).unwrap();
}
@ -257,7 +259,7 @@ fn handle_connection(mut stream: TcpStream) {
let length = contents.len();
let response = format!(
"{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
"{status_line}\r\nContent-Length: {length}\r\n\r\n\n{contents}"
);
stream.write_all(response.as_bytes()).unwrap();
@ -283,7 +285,7 @@ fn handle_connection(mut stream: TcpStream) {
let length = contents.len();
let response = format!(
"{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"
"{status_line}\r\nContent-Length: {length}\r\n\r\n\n{contents}"
);
stream.write_all(response.as_bytes()).unwrap();
@ -324,7 +326,7 @@ fn handle_connection(mut stream: TcpStream) {
let length = contents.len();
let response =
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}");
format!("{status_line}\r\nContent-Length: {length}\r\n\r\n\n{contents}");
stream.write_all(response.as_bytes()).unwrap();
}

@ -18,10 +18,10 @@
由于并发编程在现代社会非常重要因此每个主流语言都对自己的并发模型进行过权衡取舍和精心设计Rust 语言也不例外。下面的列表可以帮助大家理解不同并发模型的取舍:
- **OS 线程**, 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够。
- **事件驱动(Event driven)**, 这个名词你可能比较陌生,如果说事件驱动常常跟回调( Callback )一起使用,相信大家就恍然大悟了。这种模型性能相当的好,但最大的问题就是存在回调地狱的风险:非线性的控制流和结果处理导致了数据流向和错误传播变得难以掌控,还会导致代码可维护性和可读性的大幅降低,大名鼎鼎的 `JavaScript` 曾经就存在回调地狱。
- **OS 线程** 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够。
- **事件驱动(Event driven)** 这个名词你可能比较陌生,如果说事件驱动常常跟回调( Callback )一起使用,相信大家就恍然大悟了。这种模型性能相当的好,但最大的问题就是存在回调地狱的风险:非线性的控制流和结果处理导致了数据流向和错误传播变得难以掌控,还会导致代码可维护性和可读性的大幅降低,大名鼎鼎的 `JavaScript` 曾经就存在回调地狱。
- **协程(Coroutines)** 可能是目前最火的并发模型,`Go` 语言的协程设计就非常优秀,这也是 `Go` 语言能够迅速火遍全球的杀手锏之一。协程跟线程类似,无需改变编程模型,同时,它也跟 `async` 类似,可以支持大量的任务并发运行。但协程抽象层次过高,导致用户无法接触到底层的细节,这对于系统编程语言和自定义异步运行时是难以接受的
- **actor 模型**是 erlang 的杀手锏之一,它将所有并发计算分割成一个一个单元,这些单元被称为 `actor` , 单元之间通过消息传递的方式进行通信和数据传递,跟分布式系统的设计理念非常相像。由于 `actor` 模型跟现实很贴近,因此它相对来说更容易实现,但是一旦遇到流控制、失败重试等场景时,就会变得不太好用
- **actor 模型**是 erlang 的杀手锏之一,它将所有并发计算分割成一个一个单元,这些单元被称为 `actor` 单元之间通过消息传递的方式进行通信和数据传递,跟分布式系统的设计理念非常相像。由于 `actor` 模型跟现实很贴近,因此它相对来说更容易实现,但是一旦遇到流控制、失败重试等场景时,就会变得不太好用
- **async/await** 该模型性能高,还能支持底层编程,同时又像线程和协程那样无需过多的改变编程模型,但有得必有失,`async` 模型的问题就是内部实现机制过于复杂,对于用户来说,理解和使用起来也没有线程和协程简单,好在前者的复杂性开发者们已经帮我们封装好,而理解和使用起来不够简单,正是本章试图解决的问题。
总之Rust 经过权衡取舍后,最终选择了同时提供多线程编程和 async 编程:
@ -33,7 +33,7 @@
目前已经有诸多语言都通过 `async` 的方式提供了异步编程,例如 `JavaScript` ,但 `Rust` 在实现上有所区别:
- **Future 在 Rust 中是惰性的**,只有在被轮询(`poll`)时才会运行, 因此丢弃一个 `future` 会阻止它未来再被运行, 你可以将`Future`理解为一个在未来某个时间点被调度执行的任务。
- **Future 在 Rust 中是惰性的**,只有在被轮询(`poll`)时才会运行, 因此丢弃一个 `future` 会阻止它未来再被运行你可以将`Future`理解为一个在未来某个时间点被调度执行的任务。
- **Async 在 Rust 中使用开销是零** 意味着只有你能看到的代码(自己的代码)才有性能损耗,你看不到的(`async` 内部实现)都没有性能损耗,例如,你可以无需分配任何堆内存、也无需任何动态分发来使用 `async` 这对于热点路径的性能有非常大的好处正是得益于此Rust 的异步编程性能才会这么高。
- **Rust 没有内置异步调用所必需的运行时**但是无需担心Rust 社区生态中已经提供了非常优异的运行时实现,例如大明星 [`tokio`](https://tokio.rs)
- **运行时同时支持单线程和多线程**,这两者拥有各自的优缺点,稍后会讲
@ -48,7 +48,7 @@
而高并发更适合 `IO` 密集型任务,例如 web 服务器、数据库连接等等网络服务,因为这些任务绝大部分时间都处于等待状态,如果使用多线程,那线程大量时间会处于无所事事的状态,再加上线程上下文切换的高昂代价,让多线程做 `IO` 密集任务变成了一件非常奢侈的事。而使用`async`,既可以有效的降低 `CPU` 和内存的负担,又可以让大量的任务并发的运行,一个任务一旦处于`IO`或者其他等待(阻塞)状态,就会被立刻切走并执行另一个任务,而这里的任务切换的性能开销要远远低于使用多线程时的线程上下文切换。
事实上, `async` 底层也是基于线程实现,但是它基于线程封装了一个运行时,可以将多个任务映射到少量线程上,然后将线程切换变成了任务切换,后者仅仅是内存中的访问,因此要高效的多。
事实上 `async` 底层也是基于线程实现,但是它基于线程封装了一个运行时,可以将多个任务映射到少量线程上,然后将线程切换变成了任务切换,后者仅仅是内存中的访问,因此要高效的多。
不过`async`也有其缺点,原因是编译器会为`async`函数生成状态机,然后将整个运行时打包进来,这会造成我们编译出的二进制可执行文件体积显著增大。

@ -295,7 +295,7 @@ fn main() {
## unsafe 解决循环引用
除了使用 Rust 标准库提供的这些类型,你还可以使用 `unsafe` 里的裸指针来解决这些棘手的问题,但是由于我们还没有讲解 `unsafe`,因此这里就不进行展开,只附上[源码链接](https://github.com/sunface/rust-algos/blob/fbcdccf3e8178a9039329562c0de0fd01a3372fb/src/unsafe/self-ref.md), 挺长的,需要耐心 o_o
除了使用 Rust 标准库提供的这些类型,你还可以使用 `unsafe` 里的裸指针来解决这些棘手的问题,但是由于我们还没有讲解 `unsafe`,因此这里就不进行展开,只附上[源码链接](https://github.com/sunface/rust-algos/blob/fbcdccf3e8178a9039329562c0de0fd01a3372fb/src/unsafe/self-ref.md)挺长的,需要耐心 o_o
虽然 `unsafe` 不安全,但是在各种库的代码中依然很常见用它来实现自引用结构,主要优点如下:

@ -373,7 +373,7 @@ fn main() {
## 学习一本书:如何实现链表
最后,推荐一本专门如何实现链表的书(真是富有 Rust 特色,链表都能复杂到出书了 o_o[Learn Rust by writing Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/)
最后,推荐一本专门如何实现链表的书(真是富有 Rust 特色,链表都能复杂到出书了 o_o[Learn Rust by writing Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/)
## 总结

@ -303,7 +303,7 @@ where
}
```
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `&str` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://practice.rs/functional-programing/closure.html#closure-in-structs)
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `&str` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://practice-zh.course.rs/functional-programing/closure.html#closure-in-structs)
## 捕获作用域中的值
@ -810,5 +810,6 @@ fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> {
这块儿内容在进阶生命周期章节中有讲,这里就不再赘述,读者可移步[此处](https://course.rs/advance/lifetime/advance.html#闭包函数的消除规则)进行回顾。
## 课后习题
{{#include ../../practice.md}}
> [Rust By Practice](https://practice-zh.course.rs/functional-programing/closure.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/functional-programing/closure.md)。

@ -93,7 +93,7 @@ fn generate_id()->usize{
if current_val > MAX_ID{
panic!("Factory ids overflowed");
}
GLOBAL_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
GLOBAL_ID_COUNTER.fetch_add(1, Ordering::Relaxed);
let next_id = GLOBAL_ID_COUNTER.load(Ordering::Relaxed);
if next_id > MAX_ID{
panic!("Factory ids overflowed");

@ -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://practice-zh.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://practice-zh.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://practice-zh.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://practice-zh.course.rs/lifetime/static.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
## 总结

@ -71,7 +71,7 @@ assert_eq!(o, 8);
上例还能看出几点:
- `asm!` 允许使用多个格式化字符串,每一个作为单独一行汇编代码存在,看起来跟阅读真实的汇编代码类似
- 输入变通过 `in` 来声明
- 输入变通过 `in` 来声明
- 和以前见过的格式化字符串一样,可以使用多个参数,通过 {0}, {1} 来指定,这种方式特别有用,毕竟在代码中,变量是经常复用的,而这种参数的指定方式刚好可以复用
事实上,还可以进一步优化代码,去掉 `mov` 指令:

@ -0,0 +1,39 @@
# Rust 新版解读 | 1.75 | async trait 和 RPITIT
> Rust 1.75 官方 release doc: [Announcing Rust 1.75.0 | Rust Blog](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.75 版本:
```shell
$ rustup update stable
```
## 在 traits 里使用 `async fn``impl Trait` 形式的返回值
之前的[文章](https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html)里也提到了Rust 1.75 将支持在 traits 里使用 `async fn``impl Trait` 形式的返回值。不过如同前文所说这个版本还是有一些限制。
译者注实践上来说即使不考虑兼容低版本的rustc目前也还是需要使用主流的 async-trait 三方库来写出简洁灵活的 async traits。不过相信官方团队最终肯定会都跟进吸纳这些实用的功能的。
## 指针字节偏移API
裸指针(`*const T`和 `*mut T`以前主要用作操作某个具体的类型为T的对象。例如`<* const T>::add(1)` 会将大小是 `size_of::<T>()` 的字节数添加到指针指向的地址上。在某些情况下使用字节偏移更方便这些新API避免了调用者需要先将指针转换为 `*const u8` / `*mut u8` 的情况。
* [`pointer::byte_add`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.byte_add)
* [`pointer::byte_offset`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.byte_offset)
* [`pointer::byte_offset_from`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.byte_offset_from)
* [`pointer::byte_sub`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.byte_sub)
* [`pointer::wrapping_byte_add`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.wrapping_byte_add)
* [`pointer::wrapping_byte_offset`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.wrapping_byte_offset)
* [`pointer::wrapping_byte_sub`](https://doc.rust-lang.org/stable/core/primitive.pointer.html#method.wrapping_byte_sub)
## rustc 的代码布局优化
Rust编译器的的速度一直在优化当前新版本应用了[LLVM - BOLT](https://github.com/llvm/llvm-project/blob/main/bolt/README.md)到二进制发布包使得我们的基准测试用时时间改善了2%。这个工具优化了librustc_driver.so库的布局能更好地利用缓存。
我们现在也用 `-Ccodegen-units=1` 来构建rustc这为LLVM提供了更多的优化机会。这个优化使得我们的基准测试平均用时又提高了1.5%。
这些优化暂时仅限于x86_64-unknown-linux-gnu编译器但我们预计后续会扩展到更多平台。
## Others
其它更新细节和稳定的API列表参考[原Blog](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html#stabilized-apis)

@ -0,0 +1,44 @@
# Rust 新版解读 | 1.76 | ABI 兼容性更新
> Rust 1.76 官方 release doc: [Announcing Rust 1.76.0 | Rust Blog](https://blog.rust-lang.org/2024/02/08/Rust-1.76.0.html)
通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.76 版本:
```shell
$ rustup update stable
```
2024 新年好!
## ABI 兼容性更新
函数指针文档中新的 [ABI 兼容性部分](https://doc.rust-lang.org/stable/std/primitive.fn.html#abi-compatibility)描述了函数签名与 ABI 兼容的含义,并包括一份当前 Rust 中参数和返回值类型符合 ABI 兼容的列表。在大多数情况下,本文档不会添加任何新的保证,仅描述现有的兼容性状态。
当前保证了 `char``u32` 是 ABI 兼容的。它们始终具有相同的大小和对齐方式,也被认为是完全等效的。
## 通过引用获取类型名称描述
出于调试目的,自 Rust 1.38 起,`any::type_name::<T>()` 可用于获取类型 `T` 的字符串描述,但这需要显式写明类型。而写清楚类型并不总是那么容易,特别是对于像闭包这样的不可命名类型或不透明的返回类型。新的 `any::type_name_of_val(&T)` 允许使用任何对象的引用,来获取对应类型的描述性名称。
```rust
fn get_iter() -> impl Iterator<Item = i32> {
[1, 2, 3].into_iter()
}
fn main() {
let iter = get_iter();
let iter_name = std::any::type_name_of_val(&iter);
let sum: i32 = iter.sum();
println!("The sum of the `{iter_name}` is {sum}.");
}
```
上述代码将会打印出
```text
The sum of the `core::array::iter::IntoIter<i32, 3>` is 6.
```
## Others
其它更新细节和稳定的API列表参考[原Blog](https://blog.rust-lang.org/2024/02/08/Rust-1.76.0.html#stabilized-apis)

@ -0,0 +1,55 @@
# Rust 新版解读 | 1.77 | 异步函数支持递归
> Rust 1.77 官方 release doc: [Announcing Rust 1.77.0 | Rust Blog](https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html)
通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.77 版本:
```shell
$ rustup update stable
```
## C 字符串字面量
Rust 现在支持 C 字符串字面量(`c"abc"`),这些字面量会在内存中扩展为类型为 `&'static CStr` 的字符串,且有 `\0` (nul-byte) 作为终止符。
这使得编写与需要用到这类具有终止符的字符串的外部语言接口进行交互的代码更容易,所有相关的错误检查(例如,缺少 nul-byte )都在编译时执行。
## 支持递归调用 `async fn`
之前对于一个异步函数的递归调用,编译器会报错并推荐你使用 [crate: async_recursion](https://crates.io/crates/async_recursion)。
现在,这个限制已经被解除,且编译器会提示你使用一些间接的方式来避免函数状态的无限制增加。
```bash
error[E0733]: recursion in an async fn requires boxing
note: a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future
```
由此可以正常使用下述代码了:
```rust
async fn fib(n: u32) -> u32 {
match n {
0 | 1 => 1,
_ => Box::pin(fib(n-1)).await + Box::pin(fib(n-2)).await
}
}
```
## 宏 `offset_of!`
1.77 版本稳定了 [`offset_of!`](https://doc.rust-lang.org/stable/std/mem/macro.offset_of.html) 宏,用于获取结构体字段的字节偏移量,这个宏在需要获取字段的偏移量但没有实例的情况下非常有用。在稳定之前,实现这样的宏需要使用复杂的不安全代码,很容易引入未定义行为。
用户现在可以使用 `offset_of!(StructName, field)` 来获取公共字段的偏移量。这会展开为一个 `usize` 表达式,表示从结构体开始的字节偏移量。
## Cargo profiles 默认启用 strip
Cargo profiles 不在输出中启用 [debuginfo](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#debug)例如debug = 0的情况下将默认启用 strip = "debuginfo"。
这主要是因为(预编译的)标准库附带了 debuginfo这意味着即使本地编译没有显式请求 debuginfo静态链接的结果也会包含标准库的 debuginfo。
想要 debuginfo 的用户可以在相关的 [Cargo profile](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#debug) 中显式启用它。
## Others
其它更新细节和稳定的API列表参考[原Blog](https://blog.rust-lang.org/2024/03/21/Rust-1.77.0.html#stabilized-apis)

@ -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://practice-zh.course.rs/basic-types/char-bool-unit.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/char-bool.md)。

@ -10,7 +10,7 @@ fn add(i: i32, j: i32) -> i32 {
}
```
该函数如此简单,但是又是如此的五脏俱全,声明函数的关键字 `fn` ,函数名 `add()`,参数 `i``j`,参数类型和返回值类型都是 `i32`,总之一切那么的普通,但是又那么的自信,直到你看到了下面这张图:
该函数如此简单,但是又是如此的五脏俱全,声明函数的关键字 `fn`函数名 `add()`,参数 `i``j`,参数类型和返回值类型都是 `i32`,总之一切那么的普通,但是又那么的自信,直到你看到了下面这张图:
<img alt="" src="https://pic2.zhimg.com/80/v2-54b3a6d435d2482243edc4be9ab98153_1440w.png" class="center" />
@ -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://practice-zh.course.rs/basic-types/functions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/functions.md)。

@ -1,18 +1,18 @@
# 基本类型
当一门语言不谈类型时,你得小心,这大概率是动态语言(别拍我,我承认是废话)。但是把类型大张旗鼓的用多个章节去讲的Rust 是其中之一。
当一门语言不谈类型时,你得小心,这大概率是动态语言(别拍我,我承认是废话)。但是把类型大张旗鼓的用多个章节去讲的Rust 是其中之一。
Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:
Rust 每个值都有其确切的数据类型,总的来说可以分为两类:基本类型和复合类型。 基本类型意味着它们往往是一个最小化原子类型,无法解构为其它类型(一般意义上来说),由以下组成:
- 数值类型: 有符号整数 (`i8`, `i16`, `i32`, `i64`, `isize`)、 无符号整数 (`u8`, `u16`, `u32`, `u64`, `usize`) 、浮点数 (`f32`, `f64`)、以及有理数、复数
- 数值类型有符号整数 (`i8`, `i16`, `i32`, `i64`, `isize`)、 无符号整数 (`u8`, `u16`, `u32`, `u64`, `usize`) 、浮点数 (`f32`, `f64`)、以及有理数、复数
- 字符串:字符串字面量和字符串切片 `&str`
- 布尔类型: `true`和`false`
- 字符类型: 表示单个 Unicode 字符,存储为 4 个字节
- 单元类型: `()` ,其唯一的值也是 `()`
- 布尔类型:`true` 和 `false`
- 字符类型表示单个 Unicode 字符,存储为 4 个字节
- 单元类型`()` ,其唯一的值也是 `()`
## 类型推导与标注
与 Python、JavaScript 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 **Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在 [Rust 语言初印象](https://course.rs/first-try/hello-world.html#rust-语言初印象)中有过展示。
与 Python、JavaScript 等动态语言不同Rust 是一门静态类型语言,也就是编译器必须在编译期知道我们所有变量的类型,但这不意味着你需要为每个变量指定类型,因为 **Rust 编译器很聪明,它可以根据变量的值和上下文中的使用方式来自动推导出变量的类型**,同时编译器也不够聪明,在某些情况下,它无法推导出变量类型,需要手动去给予一个类型标注,关于这一点在 [Rust 语言初印象](https://course.rs/first-try/hello-world.html#rust-语言初印象) 中有过展示。
来看段代码:

@ -22,7 +22,7 @@ Rust 使用一个相对传统的语法来创建整数(`1``2`...)和浮
| 128 位 | `i128` | `u128` |
| 视架构而定 | `isize` | `usize` |
类型定义的形式统一为:`有无符号 + 类型大小(位数)`。**无符号数**表示数字只能取正数和0而**有符号**则表示数字可以取正数、负数还有0。就像在纸上写数字一样当要强调符号时数字前面可以带上正号或负号然而当很明显确定数字为正数时就不需要加上正号了。有符号数字以[补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。
类型定义的形式统一为:`有无符号 + 类型大小(位数)`。**无符号数**表示数字只能取正数和 0而**有符号**则表示数字可以取正数、负数还有 0。就像在纸上写数字一样当要强调符号时数字前面可以带上正号或负号然而当很明显确定数字为正数时就不需要加上正号了。有符号数字以[补码](https://en.wikipedia.org/wiki/Two%27s_complement)形式存储。
每个有符号类型规定的数字范围是 -(2<sup>n - 1</sup>) ~ 2<sup>n -
1</sup> - 1其中 `n` 是该定义形式的位长度。因此 `i8` 可存储数字范围是 -(2<sup>7</sup>) ~ 2<sup>7</sup> - 1即 -128 ~ 127。无符号类型可以存储的数字范围是 0 ~ 2<sup>n</sup> - 1所以 `u8` 能够存储的数字为 0 ~ 2<sup>8</sup> - 1即 0 ~ 255。
@ -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_*`方法的示例:
@ -107,7 +112,7 @@ fn main() {
}
```
你可能以为,这段代码没啥问题吧,实际上它会 _panic_(程序崩溃,抛出异常),因为二进制精度问题,导致了 0.1 + 0.2 并不严格等于 0.3,它们可能在小数点 N 位后存在误差。
你可能以为,这段代码没啥问题吧,实际上它会 _panic_(程序崩溃,抛出异常),因为二进制精度问题,导致了 0.1 + 0.2 并不严格等于 0.3,它们可能在小数点 N 位后存在误差。
那如果非要进行比较呢?可以考虑用这种方式 `(0.1_f64 + 0.2 - 0.3).abs() < 0.00001` ,具体小于多少,取决于你对精度的需求。
@ -156,7 +161,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display
#### NaN
对于数学上未定义的结果,例如对负数取平方根 `-42.1.sqrt()` 会产生一个特殊的结果Rust 的浮点数类型使用 `NaN` (not a number)来处理这些情况。
对于数学上未定义的结果,例如对负数取平方根 `-42.1.sqrt()` 会产生一个特殊的结果Rust 的浮点数类型使用 `NaN` (not a number) 来处理这些情况。
**所有跟 `NaN` 交互的操作,都会返回一个 `NaN`**,而且 `NaN` 不能用来比较,下面的代码会崩溃:
@ -238,15 +243,15 @@ fn main() {
## 位运算
Rust的位运算基本上和其他语言一样
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://practice-zh.course.rs/basic-types/numbers.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/numbers.md)。

@ -14,7 +14,7 @@ fn add_with_extra(x: i32, y: i32) -> i32 {
对于 Rust 语言而言,**这种基于语句statement和表达式expression的方式是非常重要的你需要能明确的区分这两个概念**, 但是对于很多其它语言而言,这两个往往无需区分。基于表达式是函数式语言的重要特征,**表达式总要返回值**。
对于 Rust 语言而言,**这种基于语句statement和表达式expression的方式是非常重要的你需要能明确的区分这两个概念**但是对于很多其它语言而言,这两个往往无需区分。基于表达式是函数式语言的重要特征,**表达式总要返回值**。
其实,在此之前,我们已经多次使用过语句和表达式。
@ -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://practice-zh.course.rs/basic-types/statements-expressions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/basic-types/statements.md)。

@ -187,13 +187,13 @@ let score: Option<&i32> = scores.get(&team_name);
- `get` 方法返回一个 `Option<&i32>` 类型:当查询不到时,会返回一个 `None`,查询到时返回 `Some(&i32)`
- `&i32` 是对 `HashMap` 中值的借用,如果不使用借用,可能会发生所有权的转移
还可以继续拓展下,上面的代码中,如果我们想直接获得值类型的 `score` 该怎么办,答案简约但不简单:
还可以继续拓展下,上面的代码中,如果我们想直接获得值类型的 `score` 该怎么办,答案简约但不简单
```rust
let score: i32 = scores.get(&team_name).copied().unwrap_or(0);
```
这里留给大家一个小作业: 去官方文档中查询下 `Option``copied` 方法和 `unwrap_or` 方法的含义及该如何使用。
这里留给大家一个小作业去官方文档中查询下 `Option``copied` 方法和 `unwrap_or` 方法的含义及该如何使用。
还可以通过循环的方式依次遍历 `KV` 对:
@ -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://practice-zh.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://practice-zh.course.rs/collections/vector.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/Vector.md)。

@ -168,7 +168,7 @@ pub mod compute;
## 文档测试(Doc Test)
相信读者之前都写过单元测试用例,其中一个很蛋疼的问题就是,随着代码的进化,单元测试用例经常会失效,过段时间后(为何是过段时间?应该这么问,有几个开发喜欢写测试用例 =,=),你发现需要连续修改不少处代码,才能让测试重新工作起来。然而,在 Rust 中,大可不必。
相信读者之前都写过单元测试用例,其中一个很蛋疼的问题就是,随着代码的进化,单元测试用例经常会失效,过段时间后(为何是过段时间?应该这么问,有几个开发喜欢写测试用例 =,=,你发现需要连续修改不少处代码,才能让测试重新工作起来。然而,在 Rust 中,大可不必。
在之前的 `add_one` 中,我们写的示例代码非常像是一个单元测试的用例,这是偶然吗?并不是。因为 Rust 允许我们在文档注释中写单元测试用例!方法就如同之前做的:
@ -472,4 +472,4 @@ Green
## 课后练习
> [Rust By Practice](https://zh.practice.rs/comments-docs.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://practice-zh.course.rs/comments-docs.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)

@ -113,7 +113,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
#### 数组元素为非基础类型
学习了上面的知识很多朋友肯定觉得已经学会了Rust的数组类型但现实会给我们一记重锤实际开发中还会碰到一种情况就是**数组元素是非基本类型**的,这时候大家一定会这样写。
学习了上面的知识,很多朋友肯定觉得已经学会了 Rust 的数组类型,但现实会给我们一记重锤,实际开发中还会碰到一种情况,就是**数组元素是非基本类型**的,这时候大家一定会这样写。
```rust
let array = [String::from("rust is good!"); 8];
@ -133,7 +133,7 @@ error[E0277]: the trait bound `String: std::marker::Copy` is not satisfied
= note: the `Copy` trait is required because this value will be copied for each element of the array
```
有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识就可以思考明白前面几个例子都是Rust的基本类型而**基本类型在Rust中赋值是以Copy的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的但很可惜复杂类型都没有深拷贝只能一个个创建。
有些还没有看过特征的小伙伴,有可能不太明白这个报错,不过这个目前可以不提,我们就拿之前所学的[所有权](https://course.rs/basic/ownership/ownership.html)知识,就可以思考明白,前面几个例子都是 Rust 的基本类型,而**基本类型在 Rust 中赋值是以 Copy 的形式**,这时候你就懂了吧,`let array=[3;5]`底层就是不断的Copy出来的但很可惜复杂类型都没有深拷贝只能一个个创建。
接着就有小伙伴会这样写。
@ -169,7 +169,7 @@ assert_eq!(slice, &[2, 3]);
- 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
- 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
- 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,`&str`字符串切片也同理
- 切片类型 [T] 拥有不固定的大小,而切片引用类型 &[T] 则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此 &[T] 更有用,`&str` 字符串切片也同理
## 总结
@ -208,7 +208,7 @@ fn main() {
做个总结,数组虽然很简单,但是其实还是存在几个要注意的点:
- **数组类型容易跟数组切片混淆**[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述
- **数组类型容易跟数组切片混淆**[T;n] 描述了一个数组的类型,而 [T] 描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用 [T;n] 的形式去描述
- `[u8; 3]`和`[u8; 4]`是不同的类型,数组的长度也是类型的一部分
- **在实际开发中,使用最多的是数组切片[T]**,我们往往通过引用的方式去使用`&[T]`,因为后者有固定的类型大小
@ -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://practice-zh.course.rs/compound-types/array.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/array.md)。

@ -18,7 +18,7 @@ enum PokerSuit {
任何一张扑克,它的花色肯定会落在四种花色中,而且也只会落在其中一个花色上,这种特性非常适合枚举的使用,因为**枚举值**只可能是其中某一个成员。抽象来看,四种花色尽管是不同的花色,但是它们都是扑克花色这个概念,因此当某个函数处理扑克花色时,可以把它们当作相同的类型进行传参。
细心的读者应该注意到,我们对之前的 `枚举类型``枚举值` 进行了重点标注,这是因为对于新人来说容易混淆相应的概念,总而言之:
**枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。**
**枚举类型是一个类型,它会包含所有可能的枚举成员而枚举值是该类型中的具体某个成员的实例。**
## 枚举值
@ -134,7 +134,7 @@ enum IpAddr {
这个例子跟我们之前的扑克牌很像,只不过枚举成员包含的类型更复杂了,变成了结构体:分别通过 `Ipv4Addr``Ipv6Addr` 来定义两种不同的 IP 数据。
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**: 例如字符串、数值、结构体甚至另一个枚举。
从这些例子可以看出,**任何类型的数据都可以放入枚举成员中**例如字符串、数值、结构体甚至另一个枚举。
增加一些挑战?先看以下代码:
@ -207,7 +207,7 @@ enum Websocket {
## Option 枚举用于处理空值
在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出**null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。
在其它编程语言中,往往都有一个 `null` 关键字,该关键字用于表明一个变量当前的值为空(不是零值,例如整型的零值是 0也就是不存在值。当你对这些 `null` 进行操作时,例如调用一个方法,就会直接抛出 **null 异常**,导致程序的崩溃,因此我们在编程时需要格外的小心去处理这些 `null` 空值。
> Tony Hoare `null` 的发明者,曾经说过一段非常有名的话:
>
@ -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://practice-zh.course.rs/compound-types/enum.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/enum.md)。

@ -30,7 +30,7 @@ fn main() {
}
```
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告,具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。
接下来我们的学习非常类似原型设计:有的方法只提供 API 接口但是不提供具体实现。此外有的变量在声明之后并未使用因此在这个阶段我们需要排除一些编译器噪音Rust 在编译的时候会扫描代码,变量声明后未使用会以 `warning` 警告的形式进行提示),引入 `#![allow(unused_variables)]` 属性标记,该标记会告诉编译器忽略未使用的变量,不要抛出 `warning` 警告具体的常见编译器属性你可以在这里查阅:[编译器属性标记](https://course.rs/profiling/compiler/attributes.html)。
`read` 函数也非常有趣,它返回一个 `!` 类型,这个表明该函数是一个发散函数,不会返回任何值,包括 `()`。`unimplemented!()` 告诉编译器该函数尚未实现,`unimplemented!()` 标记通常意味着我们期望快速完成主要代码,回头再通过搜索这些标记来完成次要代码,类似的标记还有 `todo!()`,当代码执行到这种未实现的地方时,程序会直接报错。你可以反注释 `read(&mut f1, &mut vec![]);` 这行,然后再观察下结果。

@ -95,9 +95,9 @@ let slice = &s[..];
> ```
>
> 因为我们只取 `s` 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 `中` 字都取不完整,此时程序会直接崩溃退出,如果改成 `&s[0..3]`,则可以正常通过编译。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)。
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片: `fn first_word(s: &String) -> &str `
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片`fn first_word(s: &String) -> &str `
有了切片就可以写出这样的代码:
@ -152,7 +152,7 @@ assert_eq!(slice, &[2, 3]);
## 字符串字面量是切片
之前提到过字符串字面量,但是没有提到它的类型:
之前提到过字符串字面量但是没有提到它的类型:
```rust
let s = "Hello, world!";
@ -176,7 +176,7 @@ Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF-8 编码**。
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` 和` CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` `CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
## String 与 &str 的转换
@ -394,7 +394,7 @@ string_replace_range = "I like Rust!"
#### 删除 (Delete)
与字符串删除相关的方法有 4 个,们分别是 `pop()``remove()``truncate()``clear()`。这四个方法仅适用于 `String` 类型。
与字符串删除相关的方法有 4 个,们分别是 `pop()``remove()``truncate()``clear()`。这四个方法仅适用于 `String` 类型。
1、 `pop` —— 删除并返回字符串的最后一个字符
@ -683,7 +683,7 @@ for b in "中国人".bytes() {
那么问题来了,为啥 `String` 可变,而字符串字面值 `str` 却不可以?
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串字面值的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行过程中动态生成的。
就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串字面值的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行过程中动态生成的。
对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:
@ -705,19 +705,19 @@ for b in "中国人".bytes() {
// s 不再有效,内存被释放
```
与其它系统编程语言的 `free` 函数相同Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数: 上面代码中Rust 在结尾的 `}` 处自动调用 `drop`
与其它系统编程语言的 `free` 函数相同Rust 也提供了一个释放内存的函数: `drop`,但是不同的是,其它语言要手动调用 `free` 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 `drop` 函数上面代码中Rust 在结尾的 `}` 处自动调用 `drop`
> 其实,在 C++ 中,也有这种概念: _Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。
> 其实,在 C++ 中,也有这种概念_Resource Acquisition Is Initialization (RAII)_。如果你使用过 RAII 模式的话应该对 Rust 的 `drop` 函数并不陌生。
这个模式对编写 Rust 代码的方式有着深远的影响,在后面章节我们会进行更深入的介绍。
## 课后练习
> Rust By Practice支持代码在线编辑和运行并提供详细的习题解答。
> - [字符串](https://zh.practice.rs/compound-types/string.html)
> - [字符串](https://practice-zh.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://practice-zh.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://practice-zh.course.rs/collections/String.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/collections/String.md)
<hr />

@ -125,7 +125,7 @@ fn build_user(email: String, username: String) -> User {
> 聪明的读者肯定要发问了:明明有三个字段进行了自动赋值,为何只有 `username` 发生了所有权转移?
>
> 仔细回想一下[所有权](https://course.rs/basic/ownership/ownership.html#拷贝浅拷贝)那一节的内容,我们提到了 `Copy` 特征:实现了 `Copy` 特征的类型无需所有权转移,可以直接在赋值时进行
> 数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>数据拷贝,其中 `bool``u64` 类型就实现了 `Copy` 特征,因此 `active``sign_in_count` 字段在赋值给 `user2` 时,仅仅发生了拷贝,而不是所有权转移。
>
> 值得注意的是:`username` 所有权被转移给了 `user2`,导致了 `user1` 无法再被使用,但是并不代表 `user1` 内部的其它字段不能被继续使用,例如:
@ -184,7 +184,7 @@ println!("{:?}", user1);
上面定义的 `File` 结构体在内存中的排列如下图所示:
<img alt="" src="https://pic3.zhimg.com/80/v2-8cc4ed8cd06d60f974d06ca2199b8df5_1440w.png" class="center" />
从图中可以清晰地看出 `File` 结构体两个字段 `name``data` 分别拥有底层两个 `[u8]` 数组的所有权(`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。
从图中可以清晰地看出 `File` 结构体两个字段 `name``data` 分别拥有底层两个 `[u8]` 数组的所有权`String` 类型的底层也是 `[u8]` 数组),通过 `ptr` 指针指向底层数组的内存地址,这里你可以把 `ptr` 指针理解为 Rust 中的引用类型。
该图片也侧面印证了:**把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段**。
@ -206,7 +206,7 @@ println!("{:?}", user1);
还记得之前讲过的基本没啥用的[单元类型](https://course.rs/basic/base-type/char-bool.html#单元类型)吧?单元结构体就跟它很像,没有任何字段和属性,但是好在,它还挺有用。
如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 `单元结构体`
如果你定义一个类型,但是不关心该类型的内容只关心它的行为时,就可以使用 `单元结构体`
```rust
struct AlwaysEqual;
@ -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://practice-zh.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://practice-zh.course.rs/compound-types/tuple.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/compound-types/tuple.md)。

@ -9,7 +9,7 @@
## 定义
其实项目 `Package` 和包 `Crate` 很容易被搞混,甚至在很多书中,这两者都是不分的,但是由于官方对此做了明确的区分,因此我们会在本章节中试图(挣扎着)理清这个概念。
其实项目 `Package` 和包 `Crate` 很容易被搞混,甚至在很多书中,这两者都是不分的,但是由于官方对此做了明确的区分,因此我们会在本章节中试图(挣扎着)理清这个概念。
#### 包 Crate
@ -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://practice-zh.course.rs/crate-module/crate.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/crate.md)。

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

@ -70,7 +70,7 @@ fn main() {
}
```
其实严格来说,对于引用方式并没有需要遵守的惯例,主要还是取决于你的喜好,不过我们建议:**优先使用最细粒度(引入函数、结构体等)的引用方式,如果引起了某种麻烦(例如前面两种情况),再使用引入模块的方式**。
其实严格来说,对于引用方式并没有需要遵守的惯例,主要还是取决于你的喜好,不过我们建议:**优先使用最细粒度(引入函数、结构体等)的引用方式,如果引起了某种麻烦(例如前面两种情况),再使用引入模块的方式**。
## 避免同名引用
@ -228,7 +228,7 @@ fn main() {
}
```
以上代码中,`std::collection::HashMap` 被 `*` 引入到当前作用域,但是由于存在另一个同名的结构体,因此 `HashMap::new` 根本不存在,因为对于编译器来说,本地同名类型的优先级更高。
以上代码中,`std::collections::HashMap` 被 `*` 引入到当前作用域,但是由于存在另一个同名的结构体,因此 `HashMap::new` 根本不存在,因为对于编译器来说,本地同名类型的优先级更高。
在实际项目中,这种引用方式往往用于快速写测试代码,它可以把所有东西一次性引入到 `tests` 模块中。
@ -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://practice-zh.course.rs/crate-module/use-pub.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/crate-module/use-pub.md)。

@ -8,7 +8,7 @@
> if else 无处不在 -- 鲁迅
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的 :) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
```rust
if condition == true {
@ -38,7 +38,7 @@ fn main() {
以上代码有以下几点要注意:
- **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5`
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
- 用 `if` 来赋值时,要保证每个分支返回的类型一样事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
```console
error[E0308]: if and else have incompatible types
@ -118,7 +118,7 @@ for item in &container {
}
```
> 对于实现了 `copy` 特征的数组(例如 [i32; 10] )而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr`
> 对于实现了 `copy` 特征的数组(例如 [i32; 10]而言, `for item in arr` 并不会把 `arr` 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 `arr`
如果想在循环中,**修改该元素**,可以使用 `mut` 关键字:
@ -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://practice-zh.course.rs/flow-control.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/flow-control.md)。

@ -195,7 +195,7 @@ fn main() {
## 位置参数
除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从 0 开始)
除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从 0 开始)
```rust
fn main() {
@ -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://practice-zh.course.rs/formatted-output.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
## 总结

@ -7,7 +7,7 @@
- 就像编译器大部分时候可以自动推导类型 <-> 一样,编译器大多数时候也可以自动推导生命周期
- 在多种类型存在时,编译器往往要求我们手动标明类型 <-> 当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期
Rust 生命周期之所以难,是因为这个概念对于我们来说是全新的,没有其它编程语言的经验可以借鉴。当你觉得难的时候,不用过于担心,这个难对于所有人都是平等的,多点付出就能早点解决此拦路虎,同时本书也会尽力帮助大家减少学习难度(生命周期很可能是 Rust 中最难的部分)
Rust 生命周期之所以难,是因为这个概念对于我们来说是全新的,没有其它编程语言的经验可以借鉴。当你觉得难的时候,不用过于担心,这个难对于所有人都是平等的,多点付出就能早点解决此拦路虎,同时本书也会尽力帮助大家减少学习难度(生命周期很可能是 Rust 中最难的部分)
## 悬垂指针和生命周期
@ -98,9 +98,7 @@ fn main() {
let result = longest(string1.as_str(), string2);
println!("The longest string is {}", result);
}
```
```rust
fn longest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
x
@ -132,7 +130,7 @@ help: consider introducing a named lifetime parameter // 考虑引入一个生
不过说来尴尬,就这个函数而言,我们也不知道返回值到底引用哪个,因为一个分支返回 `x`,另一个分支返回 `y`...这可咋办?先来分析下。
我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是 `if` 还是 `else` 被执行,无从得知。其次,传入引用的具体生命周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道 `x``y` 的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)
我们在定义该函数时,首先无法知道传递给函数的具体值,因此到底是 `if` 还是 `else` 被执行,无从得知。其次,传入引用的具体生命周期也无法知道,因此也不能像之前的例子那样通过分析生命周期来确定引用是否有效。同时,编译器的借用检查也无法推导出返回值的生命周期,因为它不知道 `x``y` 的生命周期跟返回值的生命周期之间的关系是怎样的(说实话,人都搞不清,何况编译器这个大聪明)
因此,这时就回到了文章开头说的内容:在存在多个引用时,编译器有时会无法自动推导生命周期,此时就需要我们手动去标注,通过为参数标注合适的生命周期来帮助编译器进行借用检查的分析。
@ -177,9 +175,9 @@ fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
需要注意的点如下:
- 和泛型一样,使用生命周期参数,需要先声明 `<'a>`
- `x`、`y` 和返回值至少活得和 `'a` 一样久(因为返回值要么是 `x`,要么是 `y`)
- `x`、`y` 和返回值至少活得和 `'a` 一样久(因为返回值要么是 `x`,要么是 `y`
该函数签名表明对于某些生命周期 `'a`,函数的两个参数都至少跟 `'a` 活得一样久,同时函数的返回引用也至少跟 `'a` 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 `'a`,但是实际上这两个参数的真实生命周期可能是不一样的(生命周期 `'a` 不代表生命周期等于 `'a`,而是大于等于 `'a`)
该函数签名表明对于某些生命周期 `'a`,函数的两个参数都至少跟 `'a` 活得一样久,同时函数的返回引用也至少跟 `'a` 活得一样久。实际上,这意味着返回值的生命周期与参数生命周期中的较小值一致:虽然两个参数的生命周期都是标注了 `'a`,但是实际上这两个参数的真实生命周期可能是不一样的生命周期 `'a` 不代表生命周期等于 `'a`,而是大于等于 `'a`
回忆下“鲁迅”说的话,再参考上面的内容,可以得出:**在通过函数签名指定生命周期参数时,我们并没有改变传入引用或者返回引用的真实生命周期,而是告诉编译器当不满足此约束条件时,就拒绝编译通过**。
@ -247,7 +245,7 @@ error[E0597]: `string2` does not live long enough
#### 深入思考生命周期标注
使用生命周期的方式往往取决于函数的功能,例如之前的 `longest` 函数,如果它永远只返回第一个参数 `x`,生命周期的标注该如何修改(该例子就是上面的小练习结果之一)?
使用生命周期的方式往往取决于函数的功能,例如之前的 `longest` 函数,如果它永远只返回第一个参数 `x`,生命周期的标注该如何修改(该例子就是上面的小练习结果之一)?
```rust
fn longest<'a>(x: &'a str, y: &str) -> &'a str {
@ -320,9 +318,9 @@ fn main() {
}
```
`ImportantExcerpt` 结构体中有一个引用类型的字段 `part`,因此需要为它标注上生命周期。结构体的生命周期标注语法跟泛型参数语法很像,需要对生命周期参数进行声明 `<'a>`。该生命周期标注说明,**结构体 `ImportantExcerpt` 所引用的字符串 `str` 必须比该结构体活得更久**。
`ImportantExcerpt` 结构体中有一个引用类型的字段 `part`,因此需要为它标注上生命周期。结构体的生命周期标注语法跟泛型参数语法很像,需要对生命周期参数进行声明 `<'a>`。该生命周期标注说明,**结构体 `ImportantExcerpt` 所引用的字符串 `str` 生命周期需要大于等于该结构体的生命周期**。
`main` 函数实现来看,`ImportantExcerpt` 的生命周期从第 4 行开始,到 `main` 函数末尾结束,而该结构体引用的字符串从第一行开始,也是到 `main` 函数末尾结束,可以得出结论**结构体引用的字符串活得比结构体久**,这符合了编译器对生命周期的要求,因此编译通过。
`main` 函数实现来看,`ImportantExcerpt` 的生命周期从第 4 行开始,到 `main` 函数末尾结束,而该结构体引用的字符串从第一行开始,也是到 `main` 函数末尾结束,可以得出结论**结构体引用的字符串生命周期大于等于结构体**,这符合了编译器对生命周期的要求,因此编译通过。
与之相反,下面的代码就无法通过编译:
@ -410,7 +408,7 @@ fn first_word<'a>(s: &'a str) -> &'a str {
例如一个引用参数的函数就有一个生命周期标注: `fn foo<'a>(x: &'a i32)`,两个引用参数的有两个生命周期标注:`fn foo<'a, 'b>(x: &'a i32, y: &'b i32)`, 依此类推。
2. **若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期
2. **若只有一个输入生命周期(函数参数中只有一个引用类型),那么该生命周期会被赋给所有的输出生命周期**,也就是所有返回值的生命周期都等于该输入生命周期
例如函数 `fn foo(x: &i32) -> &i32``x` 参数的生命周期会被自动赋给返回值 `&i32`,因此该函数等同于 `fn foo<'a>(x: &'a i32) -> &'a i32`
@ -476,7 +474,7 @@ help: consider using one of the available lifetimes here
| +++++++++
```
不得不说Rust 编译器真的很强大,还贴心的给我们提示了该如何修改,虽然。。。好像。。。。它的提示貌似不太准确。这里我们更希望参数和返回值都是 `'a` 生命周期。
不得不说Rust 编译器真的很强大,还贴心的给我们提示了该如何修改,虽然……好像……。它的提示貌似不太准确。这里我们更希望参数和返回值都是 `'a` 生命周期。
## 方法中的生命周期
@ -649,7 +647,7 @@ where
## 课后练习
> [Rust By Practice](https://zh.practice.rs/lifetime/basic.html),支持代码在线编辑和运行,并提供详细的习题解答。(本节暂无习题解答)
> [Rust By Practice](https://practice-zh.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://practice-zh.course.rs/pattern-match/patterns.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/patterns.md)。

@ -144,7 +144,7 @@ fn value_in_cents(coin: Coin) -> u8 {
上面代码中,在匹配 `Coin::Quarter(state)` 模式时,我们把它内部存储的值绑定到了 `state` 变量上,因此 `state` 变量就是对应的 `UsState` 枚举类型。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
再来看一个更复杂的例子:
@ -236,7 +236,7 @@ error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`
= note: the matched value is of type `Direction`
```
不禁想感叹Rust 的编译器**真强大**忍不住想爆粗口了sorry如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。
不禁想感叹Rust 的编译器**真强大**忍不住想爆粗口了sorry如果你以后进一步深入使用 Rust 也会像我这样感叹的。Rust 编译器清晰地知道 `match` 中有哪些分支没有被覆盖这种行为能强制我们处理所有的可能性,有效避免传说中价值**十亿美金**的 `null` 陷阱。
#### `_` 通配符
@ -342,7 +342,7 @@ assert!(matches!(bar, Some(x) if x > 2));
## 变量遮蔽
无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽:
无论是 `match` 还是 `if let`,这里都是一个新的代码块,而且这里的绑定相当于新变量,如果你使用同名变量,会发生变量遮蔽
```rust
fn main() {
@ -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://practice-zh.course.rs/pattern-match/match-iflet.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/pattern-match/match.md)。

@ -32,7 +32,7 @@ let six = plus_one(five);
let none = plus_one(None);
```
`plus_one` 接受一个 `Option<i32>` 类型的参数,同时返回一个 `Option<i32>` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。
`plus_one` 接受一个 `Option<i32>` 类型的参数,同时返回一个 `Option<i32>` 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 `None` ,则返回一个 `None` 且不做任何处理;如果传入的是一个 `Some(i32)`,则通过模式绑定,把其中的值绑定到变量 `i` 上,然后返回 `i+1` 的值,同时用 `Some` 进行包裹。
为了进一步说明,假设 `plus_one` 函数接受的参数值 x 是 `Some(5)`,来看看具体的分支匹配情况:

@ -89,13 +89,13 @@ let PATTERN = EXPRESSION;
let x = 5;
```
这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中,**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
这其中,`x` 也是一种模式绑定,代表将**匹配的值绑定到变量 x 上**。因此,在 Rust 中**变量名也是一种模式**,只不过它比较朴素很不起眼罢了。
```rust
let (x, y, z) = (1, 2, 3);
```
上面将一个元组与模式进行匹配(**模式和值的类型必需相同!**),然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。
上面将一个元组与模式进行匹配**模式和值的类型必需相同!**,然后把 `1, 2, 3` 分别绑定到 `x, y, z` 上。
模式匹配要求两边的类型必须相同,否则就会导致下面的报错:

@ -70,7 +70,7 @@ fn main() {
该例子定义了一个 `Rectangle` 结构体,并且在其上定义了一个 `area` 方法,用于计算该矩形的面积。
`impl Rectangle {}` 表示为 `Rectangle` 实现方法(`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。
`impl Rectangle {}` 表示为 `Rectangle` 实现方法`impl` 是实现 _implementation_ 的缩写),这样的写法表明 `impl` 语句块中的一切都是跟 `Rectangle` 相关联的。
#### self、&self 和 &mut self
@ -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://practice-zh.course.rs/method.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/method.md)。

@ -225,7 +225,7 @@ fn main() {
// 新编译器中r3作用域在这里结束
```
在老版本的编译器中Rust 1.31 前),将会报错,因为 `r1``r2` 的作用域在花括号 `}` 处结束,那么 `r3` 的借用就会触发 **无法同时借用可变和不可变**的规则。
在老版本的编译器中Rust 1.31 前),将会报错,因为 `r1``r2` 的作用域在花括号 `}` 处结束,那么 `r3` 的借用就会触发 **无法同时借用可变和不可变** 的规则。
但是在新的编译器中,该代码将顺利通过,因为 **引用作用域的结束位置从花括号变成最后一次使用的位置**,因此 `r1` 借用和 `r2` 借用在 `println!` 后,就结束了,此时 `r3` 可以顺利借用到可变引用。
@ -307,9 +307,9 @@ 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://practice-zh.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() {}` 中,即可运行。

@ -23,17 +23,17 @@ int* foo() {
} // 变量a和c的作用域结束
```
这段代码虽然可以编译通过,但是其实非常糟糕,变量 `a``c` 都是局部变量,函数结束后将局部变量 `a` 的地址返回,但局部变量 `a` 存在栈中,在离开作用域后,`a` 所申请的栈上内存都会被系统回收,从而造成了 `悬空指针(Dangling Pointer)` 的问题。这是一个非常典型的内存安全问题,虽然编译可以通过,但是运行的时候会出现错误, 很多编程语言都存在。
这段代码虽然可以编译通过,但是其实非常糟糕,变量 `a``c` 都是局部变量,函数结束后将局部变量 `a` 的地址返回,但局部变量 `a` 存在栈中,在离开作用域后,`a` 所申请的栈上内存都会被系统回收,从而造成了 `悬空指针(Dangling Pointer)` 的问题。这是一个非常典型的内存安全问题,虽然编译可以通过,但是运行的时候会出现错误很多编程语言都存在。
再来看变量 `c``c` 的值是常量字符串,存储于常量区,可能这个函数我们只调用了一次,也可能我们不再会使用这个字符串,但 `"xyz"` 只有当整个程序结束后系统才能回收这片内存。
所以内存安全问题,一直都是程序员非常头疼的问题,好在, 在 Rust 中这些问题即将成为历史,因为 Rust 在编译的时候就可以帮助我们发现内存不安全的问题,那 Rust 如何做到这一点呢?
所以内存安全问题,一直都是程序员非常头疼的问题,好在在 Rust 中这些问题即将成为历史,因为 Rust 在编译的时候就可以帮助我们发现内存不安全的问题,那 Rust 如何做到这一点呢?
在正式进入主题前,先来一个预热知识。
## 栈(Stack)与堆(Heap)
栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要, 因为这会影响程序的行为和性能。
栈和堆是编程语言最核心的数据结构,但是在很多语言中,你并不需要深入了解栈与堆。 但对于 Rust 这样的系统编程语言,值是位于栈上还是堆上非常重要因为这会影响程序的行为和性能。
栈和堆的核心目标就是为程序在运行时提供可供使用的内存空间。
@ -49,19 +49,16 @@ int* foo() {
与栈不同,对于大小未知或者可能变化的数据,我们需要将它存储在堆上。
当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的**指针**, 该过程被称为**在堆上分配内存**,有时简称为 “分配”(allocating)。
当向堆上放入数据时,需要请求一定大小的内存空间。操作系统在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的**指针**该过程被称为**在堆上分配内存**,有时简称为 “分配”(allocating)。
接着,该指针会被推入**栈**中,因为指针的大小是已知且固定的,在后续使用过程中,你将通过栈中的**指针**,来获取数据在堆上的实际内存位置,进而访问该数据。
由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭: 进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
由上可知,堆是一种缺乏组织的数据结构。想象一下去餐馆就座吃饭进入餐馆,告知服务员有几个人,然后服务员找到一个够大的空桌子(堆上分配的内存空间)并领你们过去。如果有人来迟了,他们也可以通过桌号(栈上的指针)来找到你们坐在哪。
#### 性能区别
写入方面:入栈比在堆上分配内存要快,因为入栈时操作系统无需分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备。
读取方面:得益于 CPU 高速缓存,使得处理器可以减少对内存的访问,高速缓存和内存的访问速度差异在 10 倍以上!栈数据往往可以直接存储在 CPU 高速缓存中,而堆数据只能存储在内存中。访问堆上的数据比访问栈上的数据慢,因为必须先访问栈再通过栈上的指针来访问内存。
因此,处理器处理分配在栈上数据会比在堆上的数据更加高效。
在栈上分配内存比在堆上分配内存要快,因为入栈时操作系统无需进行函数调用(或更慢的系统调用)来分配新的空间,只需要将新数据放入栈顶即可。相比之下,在堆上分配内存则需要更多的工作,这是因为操作系统必须首先找到一块足够存放数据的内存空间,接着做一些记录为下一次分配做准备,如果当前进程分配的内存页不足时,还需要进行系统调用来申请更多内存。
因此,处理器在栈上分配数据会比在堆上分配数据更加高效。
#### 所有权与堆栈
@ -77,12 +74,12 @@ int* foo() {
> 1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
> 2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
> 3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
> 3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
#### 变量作用域
作用域是一个变量在程序中有效的范围, 假如有这样一个变量:
作用域是一个变量在程序中有效的范围假如有这样一个变量:
```rust
let s = "hello";
@ -109,7 +106,7 @@ let s = "hello";
- **字符串字面值是不可变的**,因为被硬编码到程序代码中
- 并非所有字符串的值都能在编写代码时得知
例如,字符串是需要程序运行时,通过用户动态输入然后存储在内存中的,这种情况,字符串字面值就完全无用武之地。 为此Rust 为我们提供动态字符串类型: `String`, 该类型被分配到堆上,因此可以动态伸缩,也就能存储在编译时大小未知的文本。
例如,字符串是需要程序运行时,通过用户动态输入然后存储在内存中的,这种情况,字符串字面值就完全无用武之地。 为此Rust 为我们提供动态字符串类型: `String`该类型被分配到堆上,因此可以动态伸缩,也就能存储在编译时大小未知的文本。
可以使用下面的方法基于字符串字面量来创建 `String` 类型:
@ -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` 后就马上失效了**。
再来看看,在所有权转移后再来使用旧的所有者,会发生什么:
@ -205,7 +204,7 @@ For more information about this error, try `rustc --explain E0382`.
> 1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
> 2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
> 3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
> 3. 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)
如果你在其他语言中听说过术语 **浅拷贝(shallow copy)** 和 **深拷贝(deep copy)**,那么拷贝指针、长度和容量而不拷贝数据听起来就像浅拷贝,但是又因为 Rust 同时使第一个变量 `s1` 无效了,因此这个操作被称为 **移动(move)**,而不是浅拷贝。上面的例子可以解读为 `s1` 被**移动**到了 `s2` 中。那么具体发生了什么,用一张图简单说明:
@ -225,7 +224,7 @@ fn main() {
这段代码,大家觉得会否报错?如果参考之前的 `String` 所有权转移的例子,那这段代码也应该报错才是,但是实际上呢?
这段代码和之前的 `String` 有一个本质上的区别:在 `String` 的例子中 `s1` 持有了通过`String::from("hello")` 创建的值的所有权,而这个例子中,`x` 只是引用了存储在二进制中的字符串 `"hello, world"`,并没有持有所有权。
这段代码和之前的 `String` 有一个本质上的区别:在 `String` 的例子中 `s1` 持有了通过`String::from("hello")` 创建的值的所有权,而这个例子中,`x` 只是引用了存储在二进制可执行文件( binary )中的字符串 `"hello, world"`,并没有持有所有权。
因此 `let y = x` 中,仅仅是对该引用进行了拷贝,此时 `y``x` 都引用了同一个字符串。**如果还不理解也没关系,当学习了下一章节 "引用与借用" 后,大家自然而言就会理解。**
@ -242,9 +241,9 @@ let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
```
这段代码能够正常运行,因此说明 `s2` 确实完整的复制了 `s1` 的数据。
这段代码能够正常运行,说明 `s2` 确实完整的复制了 `s1` 的数据。
如果代码性能无关紧要,例如初始化程序时,或者在某段时间只会执行一次时,你可以使用 `clone` 来简化编程。但是对于执行较为频繁的代码(热点路径),使用 `clone` 会极大的降低程序性能,需要小心使用!
如果代码性能无关紧要,例如初始化程序时或者在某段时间只会执行寥寥数次时,你可以使用 `clone` 来简化编程。但是对于执行较为频繁的代码(热点路径),使用 `clone` 会极大的降低程序性能,需要小心使用!
#### 拷贝(浅拷贝)
@ -263,16 +262,16 @@ 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`
- 所有浮点数类型,比如 `f64`
- 字符类型,`char`
- 元组,当且仅当其包含的类型也都是 `Copy` 的时候。比如,`(i32, i32)` 是 `Copy` 的,但 `(i32, String)` 就不是
- 不可变引用 `&T` ,例如[转移所有权](#转移所有权)中的最后一个例子,**但是注意: 可变引用 `&mut T` 是不可以 Copy的**
- 不可变引用 `&T` ,例如[转移所有权](#转移所有权)中的最后一个例子,**但是注意可变引用 `&mut T` 是不可以 Copy的**
## 函数传值与返回
@ -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://practice-zh.course.rs/ownership/ownership.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/ownership/ownership.md)。

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

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

@ -47,7 +47,7 @@ fn main() {
## 泛型详解
上面代码的 `T` 就是**泛型参数**,实际上在 Rust 中,泛型参数的名称你可以任意起,但是出于惯例,我们都用 `T` ( `T``type` 的首字母)来作为首选,这个名称越短越好,除非需要表达含义,否则一个字母是最完美的。
上面代码的 `T` 就是**泛型参数**,实际上在 Rust 中,泛型参数的名称你可以任意起,但是出于惯例,我们都用 `T` `T` 是 `type` 的首字母)来作为首选,这个名称越短越好,除非需要表达含义,否则一个字母是最完美的。
使用泛型参数,有一个先决条件,必需在使用前对其进行声明:
@ -304,7 +304,7 @@ fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(arr);
let arr: [i32;2] = [1,2];
let arr: [i32; 2] = [1, 2];
display_array(arr);
}
```
@ -332,7 +332,7 @@ fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(&arr);
let arr: [i32;2] = [1,2];
let arr: [i32; 2] = [1, 2];
display_array(&arr);
}
```
@ -349,7 +349,7 @@ fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(&arr);
let arr: [i32;2] = [1,2];
let arr: [i32; 2] = [1, 2];
display_array(&arr);
}
```
@ -467,7 +467,7 @@ fn main() {
## 课后练习
> Rust By Practice支持代码在线编辑和运行并提供详细的习题解答。
> - [泛型](https://zh.practice.rs/generics-traits/generics.html)
> - [泛型](https://practice-zh.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://practice-zh.course.rs/generics-traits/const-generics.html)
> - [习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/const-generics.md)

@ -1,6 +1,6 @@
# 特征对象
在上一节中有一段代码无法通过编译:
在上一节中有一段代码无法通过编译
```rust
fn returns_summarizable(switch: bool) -> impl Summary {
@ -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://practice-zh.course.rs/generics-traits/trait-object.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/trait-object.md)。

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

@ -4,7 +4,7 @@
## 为何要手动设置变量的可变性?
在其它大多数语言中,要么只支持声明可变的变量,要么只支持声明不可变的变量( 例如函数式语言 ),前者为编程提供了灵活性,后者为编程提供了安全性,而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
在其它大多数语言中,要么只支持声明可变的变量,要么只支持声明不可变的变量(例如函数式语言),前者为编程提供了灵活性,后者为编程提供了安全性,而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
能想要学习 Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**,那么两者都要的权衡是什么呢?这就是 Rust 开发团队为我们做出的贡献,两者都要意味着 Rust 语言底层代码的实现复杂度大幅提升,因此 Salute to The Rust Team!
@ -122,7 +122,7 @@ warning: unused variable: `y`
可以看到,两个变量都是只有声明,没有使用,但是编译器却独独给出了 `y` 未被使用的警告,充分说明了 `_` 变量名前缀在这里发挥的作用。
值得注意的是,这里编译器还很善意的给出了提示( Rust 的编译器非常强大,这里的提示只是小意思 ): `y` 修改 `_y` 即可。这里就不再给出代码,留给大家手动尝试并观察下运行结果。
值得注意的是,这里编译器还很善意的给出了提示Rust 的编译器非常强大,这里的提示只是小意思):`y` 修改 `_y` 即可。这里就不再给出代码,留给大家手动尝试并观察下运行结果。
更多关于 `_x` 的使用信息,请阅读后面的[模式匹配章节](https://course.rs/basic/match-pattern/all-patterns.html?highlight=_#使用下划线开头忽略未使用的变量)。
@ -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://practice-zh.course.rs/variables.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice/blob/master/solutions/variables.md)。

@ -0,0 +1,23 @@
## Beat AI
专属于软件开发工程师的 AI 入门圣经。从神经网络到大模型,从高层设计到微观原理,从工程实现到算法,学完后,你会发现 AI 也并不是想象中那么高不可攀、无法战胜Just beat it !
- 官方地址: [https://github.com/sunface/beat-ai](https://github.com/sunface/beat-ai)
## 目前进度
准备开始动笔,预计在 2024.08 之前会完成基本的章节。
## 关于作者
作者并不是写书的小白,已经有一本 <<Rust>> ,在 Rust 领域是目前最火的书籍,备受好评。
## Q & A
Q: 为什么又双叒叕写一本 AI 入门书籍?
A: 一般来说,懂 AI 的往往并不深入懂工程,而精通工程的又不太懂 AI我们的目的就是填补这个 gap以工程师的角度来呈现 AI 的相关知识体系。
Q: 不懂数学可以学吗
A: 一本优秀的书籍它应该是**层次丰富、结构清晰、深入浅出、通俗易懂**的,能满足不同读者群体的需求。本书也是,就算读者大大不懂数学,无非就是无法深入到算法层面,但是并不妨碍深入学习理解 AI我们也会对算法进行一些深入浅出的趣味讲解。
Q: 没有软件开发的背景,不太懂工程,也不懂数学,能学吗?
A: 答案基本同上,千人千面,各有所获。

@ -23,7 +23,7 @@ pub mod webp;
`#[cfg(feature = "webp")]` 的含义是:只有在 `webp` feature 被定义后,以下的 `webp` 模块才能被引入进来。由于我们之前在 `[features]` 里定义了 `webp`,因此以上代码的 `webp` 模块会被成功引入。
`Cargo.toml` 中定义的 `feature` 会被 `Cargo` 通过命令行参数 `--cfg` 传给 `rustc`,最终由后者完成编译:`rustc --cfg ...`。若项目中的代码想要测试 `feature` 是否存在,可以使用 [`cfg` 属性](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#the-cfg-attribute)或 [`cfg` 宏](https://doc.rust-lang.org/stable/std/macro.cfg.html)。
`Cargo.toml` 中定义的 `feature` 会被 `Cargo` 通过命令行参数 `--cfg` 传给 `rustc`,最终由后者完成编译:例如,定义 "hello" 和 "hi" 两个 `feature`,等价于 `rustc --cfg 'feature="hello"' --cfg 'feature="hi" ...'`。若项目中的代码想要测试 `feature` 是否存在,可以使用 [`cfg` 属性](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#the-cfg-attribute)或 [`cfg` 宏](https://doc.rust-lang.org/stable/std/macro.cfg.html)。
之前我们提到了一个 `feature` 还可以开启其他 `feature`,举个例子,例如 ICO 图片格式包含 BMP 和 PNG 格式,因此当 `ico` 被启用后,它还得确保启用 `bmp``png`

@ -7,7 +7,10 @@
---
这个社区与其它 Rust 社区有点不一样: 我们聚焦于 Rust 语言的学习研究和实战应用上,不搞花活!
- QQ交流群: 1009730433
- RustCn 微信交流群
<img src="https://github.com/sunface/rust-course/assets/7036754/a84ec7e5-30b1-48da-9352-95503aa61a8f" width="200" />
- 公众号: `Rust语言中文网`
<img src="https://github.com/sunface/rust-course/blob/main/assets/studyrust公众号.png?raw=true" />
@ -35,4 +38,4 @@
- 想要寻找常用操作的代码片段,用于熟悉 Rust 或者直接复制粘贴到自己的项目中例如文件操作、数据库操作、HTTP 请求、排序算法、正则等
在线阅读锈书:[Github地址](https://github.com/rustlang-cn/rusty-book)
[在线阅读锈书](https://rusty.course.rs/): [Github地址](https://github.com/sunface/new-rusty-book)

@ -19,7 +19,7 @@ mod tests {
以上代码是一个测试模块,它在堆上生成了一个数组`stack`,初步看起来数组挺大的,先尝试运行下`cargo test`:
> 你很可能会遇到`#![feature(test)]`错误,因为该特性目前只存在`Rust Nightly`版本上,具体解决方法见[Rust 语言圣经](https://course.rs/appendix/rust-version.html#在指定目录使用rust-nightly)
> 你很可能会遇到`#![feature(test)]`错误,因为该特性目前只存在`Rust Nightly`版本上,具体解决方法见[Rust 语言圣经](https://course.rs/appendix/rust-version.html#在指定目录使用-rust-nightly)
```console
running 1 test

@ -6,7 +6,7 @@
Go 语言在 1.10 版本之前,所有的包都是在 `github.com` 下存放,导致了所有的项目都公用一套依赖代码,在本地项目复杂后,这简直是一种灾难。
说多了都是泪,笔者目前还有一个早期 Go 的项目 (15 年写的),用到了 `iris` (一个坑爹 HTTP 服务),结果现在运行不起来了,因为找不到 `iris` 当时的那个版本了!!
说多了都是泪,笔者目前还有一个早期 Go 的项目15 年写的),用到了 `iris` (一个坑爹 HTTP 服务),结果现在运行不起来了,因为找不到 `iris` 当时的那个版本了!!
作为一门现代化语言,`Rust` 吸收了多个语言的包管理优点,为大家提供超级大杀器: `cargo`,真的,再挑剔的开发者,都对它赞不绝口。👍

@ -54,7 +54,7 @@ World, hello
## Rust 语言初印象
Rust 这门语言对于 Haskell 和 Java 开发者来说,可能会觉得很熟悉因为它们在高阶表达方面都很优秀。简而言之就是可以很简洁的写出原本需要一大堆代码才能表达的含义。但是Rust 又有所不同:它的性能是底层语言级别的性能,可以跟 C/C++ 相媲美。
Haskell 和 Java 开发者们可能会觉得Rust 这门语言很熟悉因为它们在高阶表达方面都很优秀。简而言之就是可以很简洁的写出原本需要一大堆代码才能表达的含义。但是Rust 又有所不同:它的性能是底层语言级别的性能,可以跟 C/C++ 相媲美。
上面的 `So Easy` 的余音仍在绕梁,我希望它能继续下去,可是… 人总是要面对现实,因此让我们来点狠活:
@ -117,7 +117,7 @@ fn main() {
在终端中运行上述代码时,会看到很多 `debug: ...` 的输出,上面有讲,这些都是 `条件编译` 的输出,那么该怎么消除掉这些输出呢?
读者大大普遍冰雪聪明,肯定已经想到:是的,在 [认识 Cargo](https://course.rs/first-try/cargo.html#手动编译和运行项目)中,曾经介绍过 `--release` 参数,因为 `cargo run` 默认是运行 `debug` 模式。因此想要消灭那些 `debug:` 输出,需要更改为其它模式,其中最常用的模式就是 `--release` 也就是生产发布的模式。
读者大大普遍冰雪聪明,肯定已经想到:是的,在 [认识 Cargo](https://course.rs/first-try/cargo.html#手动编译和运行项目) 中,曾经介绍过 `--release` 参数,因为 `cargo run` 默认是运行 `debug` 模式。因此想要消灭那些 `debug:` 输出,需要更改为其它模式,其中最常用的模式就是 `--release` 也就是生产发布的模式。
具体运行代码就不给了,留给大家作为一个小练习,建议亲自动手尝试下。

@ -1,6 +1,6 @@
# 寻找牛刀,以便小试
其实对于写这种章节,我内心是拒绝的,因为真的很无趣。对于一本书而言,这也更像是一种浪费纸张的行为(好在咱无纸化-D)。不过没有办法,如果不安装 Rust 环境总不能让大家用空气运行吧so我恶趣味的起了一个这样的章节名。
其实对于写这种章节,我内心是拒绝的,因为真的很无趣。对于一本书而言,这也更像是一种浪费纸张的行为(好在咱无纸化 :-D。不过没有办法,如果不安装 Rust 环境总不能让大家用空气运行吧so我恶趣味的起了一个这样的章节名。
在本章中,你将学习以下内容:

@ -100,7 +100,7 @@ registry = "git://mirrors.ustc.edu.cn/crates.io-index"
首先,创建一个新的镜像源 `[source.ustc]`,然后将默认的 `crates-io` 替换成新的镜像源: `replace-with = 'ustc'`
简单吧?只要这样配置后,以往需要去 `crates.io` 下载的包,会全部从科大的镜像地址下载,速度刷刷的.. 我的 300M 大刀( 宽带 )终于有了用武之地。
简单吧?只要这样配置后,以往需要去 `crates.io` 下载的包,会全部从科大的镜像地址下载,速度刷刷的... 我的 300M 大刀(宽带)终于有了用武之地。
**这里强烈推荐大家在学习完后面的基本章节后,看一下 [Cargo 使用指南章节](https://course.rs/cargo/intro.html),对于你的 Rust 之旅会有莫大的帮助!**
@ -113,7 +113,7 @@ registry = "git://mirrors.ustc.edu.cn/crates.io-index"
### Blocking waiting for file lock on package cache
不过这里有一个坑,需要大家注意,如果你同时打开了 VSCODE 和命令行,然后修改了 `Cargo.toml`,此时 VSCODE 的 rust-analyzer 插件会自动检测到依赖的变更,去下载新的依赖。
在 VSCODE 下载的过程中( 特别是更新索引,可能会耗时很久),假如你又在命令行中运行类似 `cargo run` 或者 `cargo build` 的命令,就会提示一行有些看不太懂的内容:
在 VSCODE 下载的过程中(特别是更新索引,可能会耗时很久),假如你又在命令行中运行类似 `cargo run` 或者 `cargo build` 的命令,就会提示一行有些看不太懂的内容:
```shell
$ cargo build

@ -8,7 +8,7 @@ Rust 是一门全新的语言,它会带给你前所未有的体验,提升你
在学习 Go、Python 等编程语言时,你可能会一边工作、一边轻松愉快的学习它们,但是 Rust 不行。原因如文章开头所说,在学习 Rust 的同时你会收获很多语言之外的知识,因此 Rust 在入门阶段比很多编程语言要更难,但是一旦入门,你将收获一个全新的自己,成为一个更加优秀的程序员。
在学习过程中,一开始可能会轻松愉快,但是在开始接触 Rust 核心概念时(所有权、借用、生命周期、智能指针等),难度会陡然提升,此时就需要认真对待起来,否则会为后面埋下难以填补的坑: 结果最后你可能只有两个选择 - 重新学 or 放弃。
在学习过程中,一开始可能会轻松愉快,但是在开始接触 Rust 核心概念时(所有权、借用、生命周期、智能指针等),难度会陡然提升,此时就需要认真对待起来,否则会为后面埋下难以填补的坑结果最后你可能只有两个选择 - 重新学 or 放弃。
因此,在学习过程中,给大家三点建议:

@ -19,7 +19,7 @@ Rust 最早是 Mozilla 雇员 Graydon Hoare 的个人项目。从 2009 年开始
> 大家知道 Rust 的作者到底因为何事才痛下决心开发一门新的语言吗?
>
> 说来挺有趣,在 2006年的某天作者工作到精疲力尽后本想回公寓享受下生活结果发现电梯的程序出 Bug 崩溃了,要知道在国外,修理工可不像在中国那样随时待岗,还要知道,他家在 20 多楼!
> 说来挺有趣,在 2006 年的某天,作者工作到精疲力尽后,本想回公寓享受下生活,结果发现电梯的程序出 Bug 崩溃了,要知道在国外,修理工可不像在中国那样随时待岗,还要知道,他家在 20 多楼!
>
> 最后,他选择了妥协,去酒店待几天等待电梯的修理。
>
@ -112,7 +112,7 @@ Rust 的开发效率可以用先抑后扬来形容。在最初上手写项目时
- 操作系统,正在使用 Rust 开发的操作系统有好几个,其中最有名的可能就是谷歌的 FuchsiaRust 在其中扮演非常重要的角色。
- 区块链,如果 Rust 的份额说第二,应该没人敢稳说自己是第一吧?
类似的还有很多,我们就不一一列举。总之,现在有大量的项目正在被 Rust 重写同时还有海量的项目在等待被重写这些都是赚取github 星星和认可的好机会。在其它语言杀成一片红海时Rust 还留了一大片蓝海等待大家的探索!
类似的还有很多,我们就不一一列举。总之,现在有大量的项目正在被 Rust 重写,同时还有海量的项目在等待被重写,这些都是赚取 github 星星和认可的好机会。在其它语言杀成一片红海时Rust 还留了一大片蓝海等待大家的探索!
### 相比其他语言 Rust 的优势

@ -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://practice-zh.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