diff --git a/README.md b/README.md index f9c33a21..53b3b840 100644 --- a/README.md +++ b/README.md @@ -10,26 +10,34 @@ - 在线阅读 - - 官方: [https://course.rs](https://course.rs) - - 知乎: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017) + - 官网: [https://course.rs](https://course.rs) + - 知乎专栏: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017) -- 本书配套项目 - - [Rust 实战练习](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D - - [Rust 语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态 - - [Rust 酷库推荐](https://github.com/sunface/fancy-rust) Rust 优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust,它能带给你全新的体验和选择 +- 配套项目 + - [Rust语言实战](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D + - [Rust语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态 + - [Rust酷库推荐](https://github.com/sunface/fancy-rust),优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust,它能带给你全新的体验和选择 ## 教程简介 **`Rust语言圣经`**涵盖从**入门到精通**所需的 Rust 知识,目录及内容都经过深思熟虑的设计,同时语言生动幽默,行文流畅自如,摆脱技术书籍常有的机器味和晦涩感。 -在 Rust 基础教学的同时,我们还提供了(部分): +在 Rust 基础教学的同时,我们还提供了: - **深入度**,在基础教学的同时,提供了深入剖析。浅尝辄止并不能让我们站上紫禁之巅 + - **性能优化**,选择 Rust,就意味着要追求性能,因此你需要体系化地了解性能优化 -- **专题**,将 Rust 高级内容通过专题的形式一一呈现,内容内聚性极强 -- **难点和错误索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕 -- **场景化模版**,程序员上网查询如何操作文件是常事,没有人能记住所有代码,场景化模版可解君忧 + +- **专题内容**,将 Rust 高级内容通过专题的形式一一呈现,内容内聚性极强,例如手把手实现链表、Cargo和Tokio使用指南、async异步编程、标准库解析、WASM等等 + +- **内容索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕 + +- **规避陷阱和对抗编译器**,只有真的上手写过一长段时间 Rust 项目,才知道该如何规避常见的陷阱以及解决一些难搞的编译器错误,而本书将帮助你大大缩短这个过程,提前规避这些问题 + +- **Cook Book**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 + +- **配套练习题**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + Rust语言实战* 双剑合璧,给你最极致的学习体验 总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。 @@ -41,6 +49,11 @@ > 在开源版权上,我们选择了 [No License](https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwigkv-KtMT0AhXFdXAKHdI4BCcQFnoECAQQAw&url=https%3A%2F%2Fchoosealicense.com%2Fno-permission%2F&usg=AOvVaw3M2Q4IbdhnpJ2K71TF7SPB),这意味着读者可以随意的 fork 和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,望理解。 +## 学习社区 +为了帮助大家更好的学习和交流 Rust,我们建立了一个社区:**Rust语言学习社区**。 + +QQ群 1009730433, 欢迎大家加入,一起 happy,一起进步。 + ## 贡献者 @@ -51,6 +64,7 @@ - [@1132719438](https://github.com/1132719438) - [@Mintnoii](https://github.com/Mintnoii) - [@Rustln](https://github.com/rustln) +- [@zongzi531](https://github.com/zongzi531) 尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~ @@ -64,6 +78,3 @@ - 详细清单参见 [这里](./assets/writing-material/books.md) 因为它们绝大部分是支持 APACHE + MIT 双协议的,因此我们选择了遵循其中的 MIT 协议,并在这里统一对借鉴的书籍进行说明。 - -## Rust语言社区 -QQ群 1009730433, 欢迎大家加入,一起 happy,一起进步。 diff --git a/assets/Rust中英翻译对照表.md b/assets/Rust中英翻译对照表.md index 111273f2..6c825129 100644 --- a/assets/Rust中英翻译对照表.md +++ b/assets/Rust中英翻译对照表.md @@ -301,7 +301,7 @@ RAII | 资源获取即初始化(一般不译) | range | 区间,范围 | range expression | 区间表达式 | raw identifier | 原生标识符 | -raw pointer | 原生指针,裸指针 | +raw pointer | 裸指针 | RC | 引用计数 | reference counted reader | 读取器 | reader/writer | 读写器 | diff --git a/assets/sitemap.xml b/assets/sitemap.xml index 6bb6863c..bb88977c 100644 --- a/assets/sitemap.xml +++ b/assets/sitemap.xml @@ -426,62 +426,62 @@ weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly - + 2021-12-30 weekly diff --git a/src/SUMMARY.md b/src/SUMMARY.md index cfc7e5fe..b3aea018 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -1,9 +1,9 @@ # Rust 语言圣经 [进入 Rust 编程世界](into-rust.md) +[关于本书](about-book.md) [AWS 为何这么喜欢 Rust?](usecases/aws-rust.md) [避免从入门到放弃](sth-you-should-not-do.md) -[关于本书](about-book.md) [快速查询入口](index-list.md) @@ -96,64 +96,104 @@ - [Macro 宏编程](advance/macro.md) - + + - [易混淆概念解析](advance/confonding/intro.md) + - [切片和切片引用](advance/confonding/slice.md) + - [Eq 和 PartialEq](advance/confonding/eq.md) + - [String、&str 和 str todo](advance/confonding/string.md) + - [裸指针、引用和智能指针 todo](advance/confonding/pointer.md) + - [作用域、生命周期和 NLL todo](advance/confonding/lifetime.md) + - [move、Copy 和 Clone todo](advance/confonding/move-copy.md) + ## 专题内容,每个专题都配套一个小型项目进行实践 - - [自动化测试](test/intro.md) - [编写测试及控制执行](test/write-tests.md) - [单元测试和集成测试](test/unit-integration-test.md) - [断言 assertion](test/assertion.md) - [用 Github Actions 进行持续集成](test/ci.md) - [基准测试 benchmark](test/benchmark.md) + +- [Rust 异步编程](async-rust/intro.md) + - [async/await 异步编程](async-rust/async/intro.md) + - [async 编程入门](async-rust/async/getting-started.md) + - [底层探秘: Future 执行与任务调度](async-rust/async/future-excuting.md) + - [定海神针 Pin 和 Unpin](async-rust/async/pin-unpin.md) + - [async/await 和 Stream 流处理](async-rust/async/async-await.md) + - [同时运行多个 Future](async-rust/async/multi-futures-simultaneous.md) + - [一些疑难问题的解决办法](async-rust/async/pain-points-and-workarounds.md) + - [实践应用:Async Web 服务器](async-rust/async/web-server.md) + - [Tokio 使用指南](async-rust/tokio/intro.md) + - [tokio 概览](async-rust/tokio/overview.md) + - [使用初印象](async-rust/tokio/getting-startted.md) + - [创建异步任务](async-rust/tokio/spawning.md) + - [共享状态](async-rust/tokio/shared-state.md) + - [消息传递](async-rust/tokio/channels.md) + - [I/O](async-rust/tokio/io.md) + - [解析数据帧](async-rust/tokio/frame.md) + - [深入 async](async-rust/tokio/async.md) + - [select](async-rust/tokio/select.md) + - [类似迭代器的 Stream](async-rust/tokio/stream.md)) + - [优雅的关闭](async-rust/tokio/graceful-shutdown.md) + - [异步跟同步共存](async-rust/tokio/bridging-with-sync.md) -- [async/await 异步编程](async/intro.md) - - [async 编程入门](async/getting-started.md) - - [底层探秘: Future 执行与任务调度](async/future-excuting.md) - - [定海神针 Pin 和 Unpin](async/pin-unpin.md) - - [async/await 和 Stream 流处理](async/async-await.md) - - [同时运行多个 Future](async/multi-futures-simultaneous.md) - - [一些疑难问题的解决办法](async/pain-points-and-workarounds.md) - - [实践应用:Async Web 服务器](async/web-server.md) -- [Tokio 使用指南](tokio/intro.md) - - [tokio 概览](tokio/overview.md) - - [使用初印象](tokio/getting-startted.md) - - [创建异步任务](tokio/spawning.md) - - [共享状态](tokio/shared-state.md) - - [消息传递](tokio/channels.md) - - [I/O](tokio/io.md) - - [解析数据帧](tokio/frame.md) - - [深入 async](tokio/async.md) - - [select](tokio/select.md) - - [类似迭代器的 Stream](tokio/stream.md)) - - [优雅的关闭](tokio/graceful-shutdown.md) - - [异步跟同步共存](tokio/bridging-with-sync.md) - -- [Cargo 使用指南](cargo/intro.md) - - [上手使用](cargo/getting-started.md) - - [基础指南](cargo/guide/intro.md) - - [为何会有 Cargo](cargo/guide/why-exist.md) - - [下载并构建 Package](cargo/guide/download-package.md) - - [添加依赖](cargo/guide/dependencies.md) - - [Package 目录结构](cargo/guide/package-layout.md) - - [Cargo.toml vs Cargo.lock](cargo/guide/cargo-toml-lock.md) - - [测试和 CI](cargo/guide/tests-ci.md) - - [Cargo 缓存](cargo/guide/cargo-cache.md) - - [Build 缓存](cargo/guide/build-cache.md) - - [进阶指南](cargo/reference/intro.md) - - [指定依赖项](cargo/reference/specify-deps.md) - - [依赖覆盖](cargo/reference/deps-overriding.md) - - [Cargo.toml 清单详解](cargo/reference/manifest.md) - - [Cargo Target](cargo/reference/cargo-target.md) - - [工作空间 Workspace](cargo/reference/workspaces.md) - - [条件编译 Features](cargo/reference/features/intro.md) - - [Features 示例](cargo/reference/features/examples.md) - - [发布配置 Profile](cargo/reference/profiles.md) - - [通过 config.toml 对 Cargo 进行配置](cargo/reference/configuration.md) - - [发布到 crates.io](cargo/reference/publishing-on-crates.io.md) - - [构建脚本 build.rs](cargo/reference/build-script/intro.md) - - [构建脚本示例](cargo/reference/build-script/examples.md) - -- [手把手带你实现链表 doing](too-many-lists/intro.md) +- [Rust 工具链指南](toolchains/intro.md) + - [Cargo 使用指南](toolchains/cargo/intro.md) + - [上手使用](toolchains/cargo/getting-started.md) + - [基础指南](toolchains/cargo/guide/intro.md) + - [为何会有 Cargo](toolchains/cargo/guide/why-exist.md) + - [下载并构建 Package](toolchains/cargo/guide/download-package.md) + - [添加依赖](toolchains/cargo/guide/dependencies.md) + - [Package 目录结构](toolchains/cargo/guide/package-layout.md) + - [Cargo.toml vs Cargo.lock](toolchains/cargo/guide/cargo-toml-lock.md) + - [测试和 CI](toolchains/cargo/guide/tests-ci.md) + - [Cargo 缓存](toolchains/cargo/guide/cargo-cache.md) + - [Build 缓存](toolchains/cargo/guide/build-cache.md) + - [进阶指南](toolchains/cargo/reference/intro.md) + - [指定依赖项](toolchains/cargo/reference/specify-deps.md) + - [依赖覆盖](toolchains/cargo/reference/deps-overriding.md) + - [Cargo.toml 清单详解](toolchains/cargo/reference/manifest.md) + - [Cargo Target](toolchains/cargo/reference/cargo-target.md) + - [工作空间 Workspace](toolchains/cargo/reference/workspaces.md) + - [条件编译 Features](toolchains/cargo/reference/features/intro.md) + - [Features 示例](toolchains/cargo/reference/features/examples.md) + - [发布配置 Profile](toolchains/cargo/reference/profiles.md) + - [通过 config.toml 对 Cargo 进行配置](toolchains/cargo/reference/configuration.md) + - [发布到 crates.io](toolchains/cargo/reference/publishing-on-crates.io.md) + - [构建脚本 build.rs](toolchains/cargo/reference/build-script/intro.md) + - [构建脚本示例](toolchains/cargo/reference/build-script/examples.md) + +- [Rust 最佳实践](practice/intro.md) + - [对抗编译检查](practice/fight-with-compiler/intro.md) + - [生命周期](practice/fight-with-compiler/lifetime/intro.md) + - [生命周期过大-01](practice/fight-with-compiler/lifetime/too-long1.md) + - [生命周期过大-02](practice/fight-with-compiler/lifetime/too-long2.md) + - [循环中的生命周期](practice/fight-with-compiler/lifetime/loop.md) + - [闭包碰到特征对象-01](practice/fight-with-compiler/lifetime/closure-with-static.md) + - [重复借用](practice/fight-with-compiler/borrowing/intro.md) + - [同时在函数内外使用引用](practice/fight-with-compiler/borrowing/ref-exist-in-out-fn.md) + - [智能指针引起的重复借用错误](practice/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md) + - [类型未限制(todo)](practice/fight-with-compiler/unconstrained.md) + - [幽灵数据(todo)](practice/fight-with-compiler/phantom-data.md) + - [Rust 常见陷阱](practice/pitfalls/index.md) + - [for 循环中使用外部数组](practice/pitfalls/use-vec-in-for.md) + - [线程类型导致的栈溢出](practice/pitfalls/stack-overflow.md) + - [算术溢出导致的 panic](practice/pitfalls/arithmetic-overflow.md) + - [闭包中奇怪的生命周期](practice/pitfalls/closure-with-lifetime.md) + - [可变变量不可变?](practice/pitfalls/the-disabled-mutability.md) + - [可变借用失败引发的深入思考](practice/pitfalls/multiple-mutable-references.md) + - [不太勤快的迭代器](practice/pitfalls/lazy-iterators.md) + - [奇怪的序列 x..y](practice/pitfalls/weird-ranges.md) + - [无处不在的迭代器](practice/pitfalls/iterator-everywhere.md) + - [线程间传递消息导致主线程无法结束](practice/pitfalls/main-with-channel-blocked.md) + - [警惕 UTF-8 引发的性能隐患](practice/pitfalls/utf8-performance.md) + - [日常开发三方库精选](practice/third-party-libs.md) + - [命名规范](practice/naming.md) + - [面试经验 doing](practice/interview.md) + - [代码开发实践 todo](practice/best-pratice.md) + - [日志记录 todo](practice/logs.md) + - [可观测性监控 todo](practice/observability.md) + +- [手把手带你实现链表](too-many-lists/intro.md) - [我们到底需不需要链表](too-many-lists/do-we-need-it.md) - [不太优秀的单向链表:栈](too-many-lists/bad-stack/intro.md) - [数据布局](too-many-lists/bad-stack/layout.md) @@ -169,48 +209,15 @@ - [Drop、Arc 及完整代码](too-many-lists/persistent-stack/drop-arc.md) - [不咋样的双端队列](too-many-lists/deque/intro.md) - [数据布局和基本操作](too-many-lists/deque/layout.md) -- [易混淆概念解析](confonding/intro.md) - - [切片和切片引用](confonding/slice.md) - - [Eq 和 PartialEq](confonding/eq.md) - - [String、&str 和 str todo](confonding/string.md) - - [原生指针、引用和智能指针 todo](confonding/pointer.md) - - [作用域、生命周期和 NLL todo](confonding/lifetime.md) - - [move、Copy 和 Clone todo](confonding/move-copy.md) - -- [对抗编译检查 doing](fight-with-compiler/intro.md) - - [幽灵数据(todo)](fight-with-compiler/phantom-data.md) - - [生命周期](fight-with-compiler/lifetime/intro.md) - - [生命周期过大-01](fight-with-compiler/lifetime/too-long1.md) - - [生命周期过大-02](fight-with-compiler/lifetime/too-long2.md) - - [循环中的生命周期](fight-with-compiler/lifetime/loop.md) - - [闭包碰到特征对象-01](fight-with-compiler/lifetime/closure-with-static.md) - - [重复借用](fight-with-compiler/borrowing/intro.md) - - [同时在函数内外使用引用](fight-with-compiler/borrowing/ref-exist-in-out-fn.md) - - [智能指针引起的重复借用错误](fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md) - - [类型未限制(todo)](fight-with-compiler/unconstrained.md) - -- [Rust 常见陷阱](pitfalls/index.md) - - [for 循环中使用外部数组](pitfalls/use-vec-in-for.md) - - [线程类型导致的栈溢出](pitfalls/stack-overflow.md) - - [算术溢出导致的 panic](pitfalls/arithmetic-overflow.md) - - [闭包中奇怪的生命周期](pitfalls/closure-with-lifetime.md) - - [可变变量不可变?](pitfalls/the-disabled-mutability.md) - - [可变借用失败引发的深入思考](pitfalls/multiple-mutable-references.md) - - [不太勤快的迭代器](pitfalls/lazy-iterators.md) - - [奇怪的序列 x..y](pitfalls/weird-ranges.md) - - [无处不在的迭代器](pitfalls/iterator-everywhere.md) - - [线程间传递消息导致主线程无法结束](pitfalls/main-with-channel-blocked.md) - - [警惕 UTF-8 引发的性能隐患](pitfalls/utf8-performance.md) - -- [Rust 最佳实践 doing](practice/intro.md) - - [日常开发三方库精选](practice/third-party-libs.md) - - [命名规范](practice/naming.md) - - [代码开发实践 todo](practice/best-pratice.md) - - [日志记录 todo](practice/logs.md) - - [可观测性监控 todo](practice/observability.md) - - [面试经验 doing](practice/interview.md) - -- [Rust 性能剖析 todo](profiling/intro.md) + - [Peek](too-many-lists/deque/peek.md) + - [基本操作的对称镜像](too-many-lists/deque/symmetric.md) + - [迭代器](too-many-lists/deque/iterator.md) + - [最终代码](too-many-lists/deque/final-code.md) + - [不错的unsafe队列](too-many-lists/unsafe-queue/intro.md) + - [数据布局](too-many-lists/unsafe-queue/layout.md) + - [基本操作](too-many-lists/unsafe-queue/basics.md) + - [Miri](too-many-lists/unsafe-queue/miri.md) +- [Rust 性能优化 todo](profiling/intro.md) - [深入内存 todo](profiling/memory/intro.md) - [指针和引用 todo](profiling/memory/pointer-ref.md) - [未初始化内存 todo](profiling/memory/uninit.md) @@ -242,7 +249,7 @@ - [HashMap todo](std/hashmap.md) - [Iterator 常用方法 todo](std/iterator.md) -- [CookBook](cases/intro.md) +- [CookBook todo](cases/intro.md) - [命令行解析 todo](cases/cmd.md) - [配置文件解析 todo](cases/config.md) - [编解码 todo](cases/encoding/intro.md) diff --git a/src/about-book.md b/src/about-book.md index 08341cab..6c577d8b 100644 --- a/src/about-book.md +++ b/src/about-book.md @@ -12,55 +12,68 @@ - 在线阅读 - 官方: [https://course.rs](https://course.rs) - 知乎: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017) - -- 本书配套项目 - - [Rust 实战练习](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D - - [Rust 语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态 - - [Rust 酷库推荐](https://github.com/sunface/fancy-rust) Rust 优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust,它能带给你全新的体验和选择 -### 教程简介 +- 配套项目 + - [Rust语言实战](https://github.com/sunface/rust-by-practice),它是本书的配套练习册,提供了大量有挑战性的示例、练习和实践项目,帮助大家解决 Rust 语言从学习到实战的问题 — 毕竟这之间还隔着好几个 Go 语言的难度 :D + - [Rust语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态 + - [Rust酷库推荐](https://github.com/sunface/fancy-rust),优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust,它能带给你全新的体验和选择 + + +## 教程简介 **`Rust语言圣经`**涵盖从**入门到精通**所需的 Rust 知识,目录及内容都经过深思熟虑的设计,同时语言生动幽默,行文流畅自如,摆脱技术书籍常有的机器味和晦涩感。 -在 Rust 基础教学的同时,我们还提供了(部分): +在 Rust 基础教学的同时,我们还提供了: - **深入度**,在基础教学的同时,提供了深入剖析。浅尝辄止并不能让我们站上紫禁之巅 -- **性能优化**,选择 Rust,意味着就要追求性能,因此你需要体系化的了解性能优化 -- **专题**,将 Rust 高级内容通过专题的方式一一呈现,内容内聚性极强 -- **难点和错误索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕 -- **场景化模版**,程序员上网查询如何操作文件是常事,没有人能记住所有代码,场景化模版可解君忧 -总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。 +- **性能优化**,选择 Rust,就意味着要追求性能,因此你需要体系化地了解性能优化 -### 开源说明 +- **专题内容**,将 Rust 高级内容通过专题的形式一一呈现,内容内聚性极强,例如手把手实现链表、Cargo和Tokio使用指南、async异步编程、标准库解析、WASM等等 -在开源版权上,我们选择了 [No License](https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwigkv-KtMT0AhXFdXAKHdI4BCcQFnoECAQQAw&url=https%3A%2F%2Fchoosealicense.com%2Fno-permission%2F&usg=AOvVaw3M2Q4IbdhnpJ2K71TF7SPB),这意味着读者可以随意的 fork 和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,望理解。 +- **内容索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕 -Rust 语言圣经是**完全开源**的电子书,每个章节都至少用时 4-6 个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚。 +- **规避陷阱和对抗编译器**,只有真的上手写过一长段时间 Rust 项目,才知道该如何规避常见的陷阱以及解决一些难搞的编译器错误,而本书将帮助你大大缩短这个过程,提前规避这些问题 -**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个 🌟 `star`。感激不尽!:)** +- **Cook Book**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cook Book 可解君忧,Ctrl + C/V 走天下 -### 借鉴的书籍 +- **配套练习题**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + Rust语言实战* 双剑合璧,给你最极致的学习体验 -站在巨人的肩膀上,能帮我们看的更远,特此感谢以下巨人: +总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。 -- [Rust Book](https://doc.rust-lang.org/book) -- [Rust nomicon](https://doc.rust-lang.org/nomicon/dot-operator.html) -- [Async Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) -- 详细清单参见 [这里](https://github.com/sunface/rust-course/blob/main/assets/writing-material/books.md) +## ❤️ 开源 +本书是完全开源的,但是并不意味着质量上的妥协,这里的每一个章节都花费了大量的心血和时间才能完成,为此牺牲了陪伴家人、日常娱乐的时间,虽然我们并不后悔,但是如果能得到读者您的鼓励,我们将感激不尽。 -因为它们绝大部分是支持 APACHE + MIT 双协议的,因此我们选择了遵循其中的 MIT 协议,并在这里统一对借鉴的书籍进行说明。 +既然是开源,那最大的鼓励不是 money,而是 star:) **如果大家觉得这本书作者真的用心了,就帮我们点一个 🌟 吧,这将是我们继续前行最大的动力** + +> 在开源版权上,我们选择了 [No License](https://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwigkv-KtMT0AhXFdXAKHdI4BCcQFnoECAQQAw&url=https%3A%2F%2Fchoosealicense.com%2Fno-permission%2F&usg=AOvVaw3M2Q4IbdhnpJ2K71TF7SPB),这意味着读者可以随意的 fork 和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,望理解。 + + +## 学习社区 +为了帮助大家更好的学习和交流 Rust,我们建立了一个社区:**Rust学习社区**。 -### 贡献者 +QQ群 1009730433, 欢迎大家加入,一起 happy,一起进步。 + + +## 贡献者 非常感谢本教程的所有贡献者们,正是有了你们,才有了现在的高质量 Rust 教程! +- [@AllanDowney](https://github.com/AllanDowney) - [@JesseAtSZ](https://github.com/JesseAtSZ) -- [@mg-chao](https://github.com/mg-chao) - [@1132719438](https://github.com/1132719438) -- [@codemystery](https://github.com/codemystery) -- [@AllanDowney](https://github.com/AllanDowney) - [@Mintnoii](https://github.com/Mintnoii) +- [@Rustln](https://github.com/rustln) 尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~ +## 借鉴的书籍 + +站在巨人的肩膀上,能帮我们看的更远,特此感谢以下巨人: + +- [Rust Book](https://doc.rust-lang.org/book) +- [Rust nomicon](https://doc.rust-lang.org/nomicon/intro.html) +- [Async Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html) +- 详细清单参见 [这里](./assets/writing-material/books.md) + +因为它们绝大部分是支持 APACHE + MIT 双协议的,因此我们选择了遵循其中的 MIT 协议,并在这里统一对借鉴的书籍进行说明。 diff --git a/src/advance/circle-self-ref/circle-reference.md b/src/advance/circle-self-ref/circle-reference.md index 6839f544..197c87ab 100644 --- a/src/advance/circle-self-ref/circle-reference.md +++ b/src/advance/circle-self-ref/circle-reference.md @@ -295,11 +295,11 @@ 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` 不安全,但是在各种库的代码中依然很常见用它来实现自引用结构,主要优点如下: -- 性能高,毕竟直接用原生指针操作 +- 性能高,毕竟直接用裸指针操作 - 代码更简单更符合直觉: 对比下 `Option>>` ## 总结 diff --git a/src/advance/circle-self-ref/self-referential.md b/src/advance/circle-self-ref/self-referential.md index 4daf9d08..11fedf63 100644 --- a/src/advance/circle-self-ref/self-referential.md +++ b/src/advance/circle-self-ref/self-referential.md @@ -160,9 +160,9 @@ fn main() { } ``` -在这里,我们在 `pointer_to_value` 中直接存储原生指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 `unsafe` 代码。 +在这里,我们在 `pointer_to_value` 中直接存储裸指针,而不是 Rust 的引用,因此不再受到 Rust 借用规则和生命周期的限制,而且实现起来非常清晰、简洁。但是缺点就是,通过指针获取值时需要使用 `unsafe` 代码。 -当然,上面的代码你还能通过原生指针来修改 `String`,但是需要将 `*const` 修改为 `*mut`: +当然,上面的代码你还能通过裸指针来修改 `String`,但是需要将 `*const` 修改为 `*mut`: ```rust #[derive(Debug)] @@ -230,7 +230,7 @@ use std::ptr::NonNull; // 下面是一个自引用数据结构体,因为 slice 字段是一个指针,指向了 data 字段 // 我们无法使用普通引用来实现,因为违背了 Rust 的编译规则 -// 因此,这里我们使用了一个原生指针,通过 NonNull 来确保它不会为 null +// 因此,这里我们使用了一个裸指针,通过 NonNull 来确保它不会为 null struct Unmovable { data: String, slice: NonNull, @@ -272,7 +272,7 @@ fn main() { 上面的代码也非常清晰,虽然使用了 `unsafe`,其实更多的是无奈之举,跟之前的 `unsafe` 实现完全不可同日而语。 -其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的原生指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址! +其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的裸指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址! ## 使用 ouroboros diff --git a/src/advance/concurrency-with-threads/send-sync.md b/src/advance/concurrency-with-threads/send-sync.md index 44dcdb27..1caf4bcc 100644 --- a/src/advance/concurrency-with-threads/send-sync.md +++ b/src/advance/concurrency-with-threads/send-sync.md @@ -1,6 +1,6 @@ # 基于 Send 和 Sync 的线程安全 -为何 Rc、RefCell 和原生指针不可以在多线程间使用?如何让原生指针可以在多线程使用?我们一起来探寻下这些问题的答案。 +为何 Rc、RefCell 和裸指针不可以在多线程间使用?如何让裸指针可以在多线程使用?我们一起来探寻下这些问题的答案。 ## 无法用于多线程的`Rc` @@ -27,7 +27,7 @@ error[E0277]: `Rc` cannot be sent between threads safely = help: within `[closure@src/main.rs:5:27: 7:6]`, the trait `Send` is not implemented for `Rc` ``` -表面原因是`Rc`无法在线程间安全的转移,实际是编译器给予我们的那句帮助: `the trait Send is not implemented for Rc`(`Rc`未实现`Send`特征), 那么此处的`Send`特征又是何方神圣? +表面原因是`Rc`无法在线程间安全的转移,实际是编译器给予我们的那句帮助: ```the trait `Send` is not implemented for `Rc` ```(`Rc`未实现`Send`特征), 那么此处的`Send`特征又是何方神圣? ## Rc 和 Arc 源码对比 @@ -50,7 +50,7 @@ unsafe impl Sync for Arc {} `Send`和`Sync`是 Rust 安全并发的重中之重,但是实际上它们只是标记特征(marker trait,该特征未定义任何行为,因此非常适合用于标记), 来看看它们的作用: - 实现`Send`的类型可以在线程间安全的传递其所有权 -- 实现了`Sync`的类型可以在线程间安全的共享(通过引用) +- 实现`Sync`的类型可以在线程间安全的共享(通过引用) 这里还有一个潜在的依赖:一个类型要在线程间安全的共享的前提是,指向它的引用必须能在线程间传递。因为如果引用都不能被传递,我们就无法在多个线程间使用引用去访问同一个数据了。 @@ -62,7 +62,7 @@ unsafe impl Sync for Arc {} unsafe impl Sync for RwLock {} ``` -首先`RwLock`可以在线程间安全的共享,那它肯定是实现了`Sync`,但是我们的关注点不在这里。众多周知,`RwLock`可以并发的读,说明其中的值`T`必定也可以在线程间共享,那`T`必定要实现`Sync`。 +首先`RwLock`可以在线程间安全的共享,那它肯定是实现了`Sync`,但是我们的关注点不在这里。众所周知,`RwLock`可以并发的读,说明其中的值`T`必定也可以在线程间共享,那`T`必定要实现`Sync`。 果不其然,上述代码中,`T`的特征约束中就有一个`Sync`特征,那问题又来了,`Mutex`是不是相反?再来看看: @@ -80,19 +80,19 @@ unsafe impl Sync for Mutex {} 正是因为以上规则,Rust 中绝大多数类型都实现了`Send`和`Sync`,除了以下几个(事实上不止这几个,只不过它们比较常见): -- 原生指针两者都没实现,因为它本身就没有任何安全保证 +- 裸指针两者都没实现,因为它本身就没有任何安全保证 - `UnsafeCell`不是`Sync`,因此`Cell`和`RefCell`也不是 - `Rc`两者都没实现(因为内部的引用计数器不是线程安全的) -当然,如果是自定义的复合类型,那没实现那哥俩的就较为常见了:**只要复合类型中有一个成员不是`Send`或`Sync`,那么该符合类型也就不是`Send`或`Sync`**。 +当然,如果是自定义的复合类型,那没实现那哥俩的就较为常见了:**只要复合类型中有一个成员不是`Send`或`Sync`,那么该复合类型也就不是`Send`或`Sync`**。 **手动实现 `Send` 和 `Sync` 是不安全的**,通常并不需要手动实现 Send 和 Sync trait,实现者需要使用`unsafe`小心维护并发安全保证。 -至此,相关的概念大家已经掌握,但是我敢肯定,对于这两个滑不溜秋的家伙,大家依然会非常模糊,不知道它们该如何使用。那么我们来一起看看如何让原生指针可以在线程间安全的使用。 +至此,相关的概念大家已经掌握,但是我敢肯定,对于这两个滑不溜秋的家伙,大家依然会非常模糊,不知道它们该如何使用。那么我们来一起看看如何让裸指针可以在线程间安全的使用。 -## 为原生指针实现`Send` +## 为裸指针实现`Send` -上面我们提到原生指针既没实现`Send`,意味着下面代码会报错: +上面我们提到裸指针既没实现`Send`,意味着下面代码会报错: ```rust use std::thread; @@ -106,7 +106,7 @@ fn main() { } ``` -报错跟之前无二: `*mut u8 cannot be sent between threads safely`, 但是有一个问题,我们无法为其直接实现`Send`特征,好在可以用[`newtype`类型](../custom-type.md#newtype) :`struct MyBox(*mut u8);`。 +报错跟之前无二: ``` `*mut u8` cannot be sent between threads safely```, 但是有一个问题,我们无法为其直接实现`Send`特征,好在可以用[`newtype`类型](../into-types/custom-type.md#newtype) :`struct MyBox(*mut u8);`。 还记得之前的规则吗:复合类型中有一个成员没实现`Send`,该复合类型就不是`Send`,因此我们需要手动为它实现: @@ -128,7 +128,7 @@ fn main() { 此时,我们的指针已经可以欢快的在多线程间撒欢,以上代码很简单,但有一点需要注意:`Send`和`Sync`是`unsafe`特征,实现时需要用`unsafe`代码块包裹。 -## 为原生指针实现`Sync` +## 为裸指针实现`Sync` 由于`Sync`是多线程间共享一个值,大家可能会想这么实现: @@ -188,9 +188,9 @@ unsafe impl Sync for MyBox {} ## 总结 -通过上面的两个原生指针的例子,我们了解了如何实现`Send`和`Sync`,以及如何只实现`Send`而不实现`Sync`,简单总结下: +通过上面的两个裸指针的例子,我们了解了如何实现`Send`和`Sync`,以及如何只实现`Send`而不实现`Sync`,简单总结下: 1. 实现`Send`的类型可以在线程间安全的传递其所有权, 实现`Sync`的类型可以在线程间安全的共享(通过引用) -2. 绝大部分类型都实现了`Send`和`Sync`,常见的未实现的有:原生指针、Cell/RefCell、Rc 等 +2. 绝大部分类型都实现了`Send`和`Sync`,常见的未实现的有:裸指针、`Cell`、`RefCell`、`Rc` 等 3. 可以为自定义类型实现`Send`和`Sync`,但是需要`unsafe`代码块 -4. 可以为部分 Rust 中的类型实现`Send`、`Sync`,但是需要使用`newtype`,例如文中的原生指针例子 +4. 可以为部分 Rust 中的类型实现`Send`、`Sync`,但是需要使用`newtype`,例如文中的裸指针例子 diff --git a/src/advance/concurrency-with-threads/sync1.md b/src/advance/concurrency-with-threads/sync1.md index 182c6897..cec9baef 100644 --- a/src/advance/concurrency-with-threads/sync1.md +++ b/src/advance/concurrency-with-threads/sync1.md @@ -62,6 +62,24 @@ fn main() { 正因为智能指针的使用,使得我们无需任何操作就能获取其中的数据。 如果释放锁,你需要做的仅仅是做好锁的作用域管理,例如上述代码的内部花括号使用,建议读者尝试下去掉内部的花括号,然后再次尝试获取第二个锁`num1`,看看会发生什么,友情提示:不会报错,但是主线程会永远阻塞,因为不幸发生了死锁。 +```rust +use std::sync::Mutex; + +fn main() { + let m = Mutex::new(5); + + let mut num = m.lock().unwrap(); + *num = 6; + // 锁还没有被 drop 就尝试申请下一个锁,导致主线程阻塞 + // drop(num); // 手动 drop num ,可以让 num1 申请到下个锁 + let mut num1 = m.lock().unwrap(); + *num1 = 7; + // drop(num1); // 手动 drop num1 ,观察打印结果的不同 + + println!("m = {:?}", m); +} +``` + #### 多线程中使用 Mutex 单线程中使用锁,说实话纯粹是为了演示功能,毕竟多线程才是锁的舞台。 现在,我们再来看看,如何在多线程下使用`Mutex`来访问同一个资源. @@ -114,14 +132,14 @@ error[E0277]: `Rc>` cannot be sent between threads safely // `Rc`无法在线程中安全的传输 --> src/main.rs:11:22 | -11 | let handle = thread::spawn(move || { +13 | let handle = thread::spawn(move || { | ______________________^^^^^^^^^^^^^_- | | | | | `Rc>` cannot be sent between threads safely -12 | | let mut num = counter.lock().unwrap(); -13 | | -14 | | *num += 1; -15 | | }); +14 | | let mut num = counter.lock().unwrap(); +15 | | +16 | | *num += 1; +17 | | }); | |_________- within this `[closure@src/main.rs:11:36: 15:10]` | = help: within `[closure@src/main.rs:11:36: 15:10]`, the trait `Send` is not implemented for `Rc>` @@ -231,22 +249,22 @@ fn main() { for _ in 0..1 { // 线程1 if i_thread % 2 == 0 { - // 锁住mutex1 + // 锁住MUTEX1 let guard: MutexGuard = MUTEX1.lock().unwrap(); - println!("线程 {} 锁住了mutex1,接着准备去锁mutex2 !", i_thread); + println!("线程 {} 锁住了MUTEX1,接着准备去锁MUTEX2 !", i_thread); - // 当前线程睡眠一小会儿,等待线程2锁住mutex2 + // 当前线程睡眠一小会儿,等待线程2锁住MUTEX2 sleep(Duration::from_millis(10)); - // 去锁mutex2 + // 去锁MUTEX2 let guard = MUTEX2.lock().unwrap(); // 线程2 } else { - // 锁住mutex2 + // 锁住MUTEX2 let _guard = MUTEX2.lock().unwrap(); - println!("线程 {} 锁住了mutex2, 准备去锁mutex1", i_thread); + println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread); let _guard = MUTEX1.lock().unwrap(); } @@ -265,9 +283,9 @@ fn main() { 在上面的描述中,我们用了"可能"二字,原因在于死锁在这段代码中不是必然发生的,总有一次运行你能看到最后一行打印输出。这是由于子线程的初始化顺序和执行速度并不确定,我们无法确定哪个线程中的锁先被执行,因此也无法确定两个线程对锁的具体使用顺序。 -但是,可以简单的说明下死锁发生的必然条件:线程 1 锁住了`mutex1`并且线程`2`锁住了`mutex2`,然后线程 1 试图去访问`mutex2`,同时线程`2`试图去访问`mutex1`,就会死锁。 因为线程 2 需要等待线程 1 释放`mutex1`后,才会释放`mutex2`,而与此同时,线程 1 需要等待线程 2 释放`mutex2`后才能释放`mutex1`,这种情况造成了两个线程都无法释放对方需要的锁,最终死锁。 +但是,可以简单的说明下死锁发生的必然条件:线程 1 锁住了`MUTEX1`并且线程`2`锁住了`MUTEX2`,然后线程 1 试图去访问`MUTEX2`,同时线程`2`试图去访问`MUTEX1`,就会死锁。 因为线程 2 需要等待线程 1 释放`MUTEX1`后,才会释放`MUTEX2`,而与此同时,线程 1 需要等待线程 2 释放`MUTEX2`后才能释放`MUTEX1`,这种情况造成了两个线程都无法释放对方需要的锁,最终死锁。 -那么为何某些时候,死锁不会发生?原因很简单,线程 2 在线程 1 锁`mutex1`之前,就已经全部执行完了,随之线程 2 的`mutex2`和`mutex1`被全部释放,线程 1 对锁的获取将不再有竞争者。 同理,线程 1 若全部被执行完,那线程 2 也不会被锁,因此我们在线程 1 中间加一个睡眠,增加死锁发生的概率。如果你在线程 2 中同样的位置也增加一个睡眠,那死锁将必然发生! +那么为何某些时候,死锁不会发生?原因很简单,线程 2 在线程 1 锁`MUTEX1`之前,就已经全部执行完了,随之线程 2 的`MUTEX2`和`MUTEX1`被全部释放,线程 1 对锁的获取将不再有竞争者。 同理,线程 1 若全部被执行完,那线程 2 也不会被锁,因此我们在线程 1 中间加一个睡眠,增加死锁发生的概率。如果你在线程 2 中同样的位置也增加一个睡眠,那死锁将必然发生! #### try_lock @@ -292,26 +310,26 @@ fn main() { for _ in 0..1 { // 线程1 if i_thread % 2 == 0 { - // 锁住mutex1 + // 锁住MUTEX1 let guard: MutexGuard = MUTEX1.lock().unwrap(); - println!("线程 {} 锁住了mutex1,接着准备去锁mutex2 !", i_thread); + println!("线程 {} 锁住了MUTEX1,接着准备去锁MUTEX2 !", i_thread); - // 当前线程睡眠一小会儿,等待线程2锁住mutex2 + // 当前线程睡眠一小会儿,等待线程2锁住MUTEX2 sleep(Duration::from_millis(10)); - // 去锁mutex2 + // 去锁MUTEX2 let guard = MUTEX2.try_lock(); - println!("线程1获取mutex2锁的结果: {:?}",guard); + println!("线程1获取MUTEX2锁的结果: {:?}",guard); // 线程2 } else { - // 锁住mutex2 + // 锁住MUTEX2 let _guard = MUTEX2.lock().unwrap(); - println!("线程 {} 锁住了mutex2, 准备去锁mutex1", i_thread); + println!("线程 {} 锁住了MUTEX2, 准备去锁MUTEX1", i_thread); sleep(Duration::from_millis(10)); let guard = MUTEX1.try_lock(); - println!("线程2获取mutex1锁的结果: {:?}",guard); + println!("线程2获取MUTEX1锁的结果: {:?}",guard); } } })); @@ -329,10 +347,10 @@ fn main() { 为了演示`try_lock`的作用,我们特定使用了之前必定会死锁的代码,并且将`lock`替换成`try_lock`,与之前的结果不同,这段代码将不会再有死锁发生: ```console -线程 0 锁住了mutex1,接着准备去锁mutex2 ! -线程 1 锁住了mutex2, 准备去锁mutex1 -线程2获取mutex1锁的结果: Err("WouldBlock") -线程1获取mutex2锁的结果: Ok(0) +线程 0 锁住了MUTEX1,接着准备去锁MUTEX2 ! +线程 1 锁住了MUTEX2, 准备去锁MUTEX1 +线程2获取MUTEX1锁的结果: Err("WouldBlock") +线程1获取MUTEX2锁的结果: Ok(0) 死锁没有发生 ``` diff --git a/src/advance/concurrency-with-threads/sync2.md b/src/advance/concurrency-with-threads/sync2.md index 0b9af5fc..92fdb126 100644 --- a/src/advance/concurrency-with-threads/sync2.md +++ b/src/advance/concurrency-with-threads/sync2.md @@ -166,7 +166,7 @@ Y = 3; Y *= 2; X = 2; } ``` -还是可能出现`Y=2`,因为`Main`线程中的`X`和`Y`被同步到其它 CPU 缓存中的顺序未必一致。 +还是可能出现`Y = 2`,因为`Main`线程中的`X`和`Y`被同步到其它 CPU 缓存中的顺序未必一致。 #### 限定内存顺序的 5 个规则 diff --git a/src/confonding/cow.md b/src/advance/confonding/cow.md similarity index 100% rename from src/confonding/cow.md rename to src/advance/confonding/cow.md diff --git a/src/confonding/eq.md b/src/advance/confonding/eq.md similarity index 100% rename from src/confonding/eq.md rename to src/advance/confonding/eq.md diff --git a/src/confonding/intro.md b/src/advance/confonding/intro.md similarity index 100% rename from src/confonding/intro.md rename to src/advance/confonding/intro.md diff --git a/src/confonding/lifetime.md b/src/advance/confonding/lifetime.md similarity index 100% rename from src/confonding/lifetime.md rename to src/advance/confonding/lifetime.md diff --git a/src/confonding/move-copy.md b/src/advance/confonding/move-copy.md similarity index 100% rename from src/confonding/move-copy.md rename to src/advance/confonding/move-copy.md diff --git a/src/advance/confonding/pointer.md b/src/advance/confonding/pointer.md new file mode 100644 index 00000000..e07a9fcb --- /dev/null +++ b/src/advance/confonding/pointer.md @@ -0,0 +1 @@ +# 裸指针、引用和智能指针 todo diff --git a/src/confonding/slice.md b/src/advance/confonding/slice.md similarity index 100% rename from src/confonding/slice.md rename to src/advance/confonding/slice.md diff --git a/src/confonding/string.md b/src/advance/confonding/string.md similarity index 100% rename from src/confonding/string.md rename to src/advance/confonding/string.md diff --git a/src/advance/lifetime/advance.md b/src/advance/lifetime/advance.md index d00bb683..014fdf5c 100644 --- a/src/advance/lifetime/advance.md +++ b/src/advance/lifetime/advance.md @@ -136,7 +136,7 @@ error[E0499]: cannot borrow `*map` as mutable more than once at a time 不安全代码(`unsafe`)经常会凭空产生引用或生命周期,这些生命周期被称为是 **无界(unbound)** 的。 -无界生命周期往往是在解引用一个原生指针(裸指针 raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期: +无界生命周期往往是在解引用一个裸指针(裸指针 raw pointer)时产生的,换句话说,它是凭空产生的,因为输入参数根本就没有这个生命周期: ```rust fn f<'a, T>(x: *const T) -> &'a T { diff --git a/src/advance/lifetime/static.md b/src/advance/lifetime/static.md index 89607cbb..619aae5f 100644 --- a/src/advance/lifetime/static.md +++ b/src/advance/lifetime/static.md @@ -57,7 +57,7 @@ fn get_memory_location() -> (usize, usize) { } fn get_str_at_location(pointer: usize, length: usize) -> &'static str { - // 使用原生指针需要 `unsafe{}` 语句块 + // 使用裸指针需要 `unsafe{}` 语句块 unsafe { from_utf8_unchecked(from_raw_parts(pointer as *const u8, length)) } } @@ -68,7 +68,7 @@ fn main() { "The {} bytes at 0x{:X} stored: {}", length, pointer, message ); - // 如果大家想知道为何处理原生指针需要 `unsafe`,可以试着反注释以下代码 + // 如果大家想知道为何处理裸指针需要 `unsafe`,可以试着反注释以下代码 // let message = get_str_at_location(1000, 10); } ``` diff --git a/src/advance/macro.md b/src/advance/macro.md index 1843a754..230978da 100644 --- a/src/advance/macro.md +++ b/src/advance/macro.md @@ -320,7 +320,7 @@ hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } # hello_macro_derive = { path = "./hello_macro_derive" } ``` -此时,`hello_macro` 项目就可以成功的引用到 `hello_macro_derive` 本地包了,对于项目依赖引入的详细介绍,可以参见 [Cargo 章节](https://course.rs/cargo/dependency.html)。 +此时,`hello_macro` 项目就可以成功的引用到 `hello_macro_derive` 本地包了,对于项目依赖引入的详细介绍,可以参见 [Cargo 章节](https://course.rs/toolchains/cargo/dependency.html)。 接下来,就到了重头戏环节,一起来看看该如何定义过程宏。 diff --git a/src/advance/unsafe/intro.md b/src/advance/unsafe/intro.md index 28d5cd57..f13e16e7 100644 --- a/src/advance/unsafe/intro.md +++ b/src/advance/unsafe/intro.md @@ -42,17 +42,17 @@ fn main() { } ``` -上面代码中, `r1` 是一个原生指针(又称裸指针,raw pointer),由于它具有破坏 Rust 内存安全的潜力,因此只能在 `unsafe` 代码块中使用,如果你去掉 `unsafe {}`,编译器会立刻报错。 +上面代码中, `r1` 是一个裸指针(又称裸指针,raw pointer),由于它具有破坏 Rust 内存安全的潜力,因此只能在 `unsafe` 代码块中使用,如果你去掉 `unsafe {}`,编译器会立刻报错。 言归正传, `unsafe` 能赋予我们 5 种超能力,这些能力在安全的 Rust 代码中是无法获取的: -- 解引用原生指针,就如上例所示 +- 解引用裸指针,就如上例所示 - 调用一个 `unsafe` 或外部的函数 - 访问或修改一个可变的[静态变量](https://course.rs/advance/global-variable.html#静态变量) - 实现一个 `unsafe` 特征 - 访问 `union` 中的字段 -在本章中,我们将着重讲解原生指针和 FFI 的使用。 +在本章中,我们将着重讲解裸指针和 FFI 的使用。 ## unsafe 的安全保证 @@ -60,7 +60,7 @@ fn main() { 首先,`unsafe` 并不能绕过 Rust 的借用检查,也不能关闭任何 Rust 的安全检查规则,例如当你在 `unsafe` 中使用**引用**时,该有的检查一样都不会少。 -因此 `unsafe` 能给大家提供的也仅仅是之前的 5 种超能力,在使用这 5 种能力时,编译器才不会进行内存安全方面的检查,最典型的就是使用**原生指针**(引用和原生指针有很大的区别)。 +因此 `unsafe` 能给大家提供的也仅仅是之前的 5 种超能力,在使用这 5 种能力时,编译器才不会进行内存安全方面的检查,最典型的就是使用**裸指针**(引用和裸指针有很大的区别)。 ## 谈虎色变? diff --git a/src/advance/unsafe/superpowers.md b/src/advance/unsafe/superpowers.md index 5be4d80b..aa14868b 100644 --- a/src/advance/unsafe/superpowers.md +++ b/src/advance/unsafe/superpowers.md @@ -2,24 +2,24 @@ 古龙有一部小说,名为"七种兵器",其中每一种都精妙绝伦,令人闻风丧胆,而 `unsafe` 也有五种兵器,它们可以让你拥有其它代码无法实现的能力,同时它们也像七种兵器一样令人闻风丧胆,下面一起来看看庐山真面目。 -## 解引用原生指针 +## 解引用裸指针 -原生指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,原生指针长这样: `*const T` 和 `*mut T`,它们分别代表了不可变和可变。 +裸指针(raw pointer) 又称裸指针,在功能上跟引用类似,同时它也需要显式地注明可变性。但是又和引用有所不同,裸指针长这样: `*const T` 和 `*mut T`,它们分别代表了不可变和可变。 -大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在原生指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。 +大家在之前学过 `*` 操作符,知道它可以用于解引用,但是在裸指针 `*const T` 中,这里的 `*` 只是类型名称的一部分,并没有解引用的含义。 -至此,我们已经学过三种类似指针的概念:引用、智能指针和原生指针。与前两者不同,原生指针: +至此,我们已经学过三种类似指针的概念:引用、智能指针和裸指针。与前两者不同,裸指针: - 可以绕过 Rust 的借用规则,可以同时拥有一个数据的可变、不可变指针,甚至还能拥有多个可变的指针 - 并不能保证指向合法的内存 - 可以是 `null` - 没有实现任何自动的回收 (drop) -总之,原生指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。 +总之,裸指针跟 C 指针是非常像的,使用它需要以牺牲安全性为前提,但我们获得了更好的性能,也可以跟其它语言或硬件打交道。 -#### 基于引用创建原生指针 +#### 基于引用创建裸指针 -下面的代码**基于值的引用**同时创建了可变和不可变的原生指针: +下面的代码**基于值的引用**同时创建了可变和不可变的裸指针: ```rust let mut num = 5; @@ -28,9 +28,9 @@ let r1 = &num as *const i32; let r2 = &mut num as *mut i32; ``` -`as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的原生指针 `*const i32 / *mut i32`。 +`as` 可以用于强制类型转换,在[之前章节](https://course.rs/basic/converse.html)中有讲解。在这里,我们将引用 `&num / &mut num` 强转为相应的裸指针 `*const i32 / *mut i32`。 -细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建原生指针是安全的行为,而解引用原生指针才是不安全的行为** : +细心的同学可能会发现,在这段代码中并没有 `unsafe` 的身影,原因在于:**创建裸指针是安全的行为,而解引用裸指针才是不安全的行为** : ```rust fn main() { @@ -44,16 +44,16 @@ fn main() { } ``` -#### 基于内存地址创建原生指针 +#### 基于内存地址创建裸指针 -在上面例子中,我们基于现有的引用来创建原生指针,这种行为是很安全的。但是接下来的方式就不安全了: +在上面例子中,我们基于现有的引用来创建裸指针,这种行为是很安全的。但是接下来的方式就不安全了: ```rust let address = 0x012345usize; let r = address as *const i32; ``` -这里基于一个内存地址来创建原生指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。 +这里基于一个内存地址来创建裸指针,可以想像,这种行为是相当危险的。试图使用任意的内存地址往往是一种未定义的行为(undefined behavior),因为该内存地址有可能存在值,也有可能没有,就算有值,也大概率不是你需要的值。 同时编译器也有可能会优化这段代码,会造成没有任何内存访问发生,甚至程序还可能发生段错误(segmentation fault)。**总之,你几乎没有好的理由像上面这样实现代码,虽然它是可行的**。 @@ -82,7 +82,7 @@ fn main() { "The {} bytes at 0x{:X} stored: {}", length, pointer, message ); - // 如果大家想知道为何处理原生指针需要 `unsafe`,可以试着反注释以下代码 + // 如果大家想知道为何处理裸指针需要 `unsafe`,可以试着反注释以下代码 // let message = get_str_at_location(1000, 10); } ``` @@ -100,13 +100,13 @@ unsafe { } ``` -使用 `*` 可以对原生指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。 +使用 `*` 可以对裸指针进行解引用,由于该指针的内存安全性并没有任何保证,因此我们需要使用 `unsafe` 来包裹解引用的逻辑(切记,`unsafe` 语句块的范围一定要尽可能的小,具体原因在上一章节有讲)。 -以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是原生指针,需要小心。 +以上代码另一个值得注意的点就是:除了使用 `as` 来显式的转换,我们还使用了隐式的转换方式 `let c: *const i32 = &a;`。在实际使用中,我们建议使用 `as` 来转换,因为这种显式的方式更有助于提醒用户:你在使用的指针是裸指针,需要小心。 -#### 基于智能指针创建原生指针 +#### 基于智能指针创建裸指针 -还有一种创建原生指针的方式,那就是基于智能指针来创建: +还有一种创建裸指针的方式,那就是基于智能指针来创建: ```rust let a: Box = Box::new(10); @@ -118,9 +118,9 @@ let c: *const i32 = Box::into_raw(a); #### 小结 -像之前代码演示的那样,使用原生指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust,你是无法做到这一点的,违背了借用规则,编译器会对我们进行无情的阻止。因此原生指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心! +像之前代码演示的那样,使用裸指针可以让我们创建两个可变指针都指向同一个数据,如果使用安全的 Rust,你是无法做到这一点的,违背了借用规则,编译器会对我们进行无情的阻止。因此裸指针可以绕过借用规则,但是由此带来的数据竞争问题,就需要大家自己来处理了,总之,需要小心! -既然这么危险,为何还要使用原生指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。 +既然这么危险,为何还要使用裸指针?除了之前提到的性能等原因,还有一个重要用途就是跟 `C` 语言的代码进行交互( FFI ),在讲解 FFI 之前,先来看看如何调用 unsafe 函数或方法。 ## 调用 unsafe 函数或方法 @@ -223,13 +223,13 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { 相比安全实现,这段代码就显得没那么好理解了,甚至于我们还需要像 C 语言那样,通过指针地址的偏移去控制数组的分割。 -- `as_mut_ptr` 会返回指向 `slice` 首地址的原生指针 `*mut i32` +- `as_mut_ptr` 会返回指向 `slice` 首地址的裸指针 `*mut i32` - `slice::from_raw_parts_mut` 函数通过指针和长度来创建一个新的切片,简单来说,该切片的初始地址是 `ptr`,长度为 `mid` - `ptr.add(mid)` 可以获取第二个切片的初始地址,由于切片中的元素是 `i32` 类型,每个元素都占用了 4 个字节的内存大小,因此我们不能简单的用 `ptr + mid` 来作为初始地址,而应该使用 `ptr + 4 * mid`,但是这种使用方式并不安全,因此 `.add` 方法是最佳选择 -由于 `slice::from_raw_parts_mut` 使用原生指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。 +由于 `slice::from_raw_parts_mut` 使用裸指针作为参数,因此它是一个 `unsafe fn`,我们在使用它时,就必须用 `unsafe` 语句块进行包裹,类似的,`.add` 方法也是如此(还是那句话,不要将无关的代码包含在 `unsafe` 语句块中)。 -部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的原生指针 `ptr` 和 `ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了原生指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。 +部分同学可能会有疑问,那这段代码我们怎么保证 `unsafe` 中使用的裸指针 `ptr` 和 `ptr.add(mid)` 是合法的呢?秘诀就在于 `assert!(mid <= len);` ,通过这个断言,我们保证了裸指针一定指向了 `slice` 切片中的某个元素,而不是一个莫名其妙的内存地址。 再回到我们的主题:**虽然 split_at_mut 使用了 `unsafe`,但我们无需将其声明为 `unsafe fn`**,这种情况下就是使用安全的抽象包裹 `unsafe` 代码,这里的 `unsafe` 使用是非常安全的,因为我们从合法数据中创建了的合法指针。 @@ -315,7 +315,7 @@ pub extern "C" fn call_from_c() { ## 实现 unsafe 特征 -说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为原生指针实现sync) 章节中实现过 `unsafe` 特征 `Send`。 +说实话,`unsafe` 的特征确实不多见,如果大家还记得的话,我们在之前的 [Send 和 Sync](https://course.rs/advance/concurrency-with-threads/send-sync.html#为裸指针实现sync) 章节中实现过 `unsafe` 特征 `Send`。 之所以会有 `unsafe` 的特征,是因为该特征至少有一个方法包含有编译器无法验证的内容。`unsafe` 特征的声明很简单: @@ -333,7 +333,7 @@ fn main() {} 通过 `unsafe impl` 的使用,我们告诉编译器:相应的正确性由我们自己来保证。 -再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为原生指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。 +再回到刚提到的 `Send` 特征,若我们的类型中的所有字段都实现了 `Send` 特征,那该类型也会自动实现 `Send`。但是如果我们想要为某个类型手动实现 `Send` ,例如为裸指针,那么就必须使用 `unsafe`,相关的代码在之前的链接中也有,大家可以移步查看。 总之,`Send` 特征标记为 `unsafe` 是因为 Rust 无法验证我们的类型是否能在线程间安全的传递,因此就需要通过 `unsafe` 来告诉编译器,它无需操心,剩下的交给我们自己来处理。 diff --git a/src/appendix/keywords.md b/src/appendix/keywords.md index 2f53b443..70f33010 100644 --- a/src/appendix/keywords.md +++ b/src/appendix/keywords.md @@ -26,7 +26,7 @@ - `match` - 模式匹配 - `mod` - 定义一个模块 - `move` - 使闭包获取其所捕获项的所有权 -- `mut` - 在引用、原生指针或模式绑定中使用,表明变量是可变的 +- `mut` - 在引用、裸指针或模式绑定中使用,表明变量是可变的 - `pub` - 表示结构体字段、`impl` 块或模块的公共可见性 - `ref` - 通过引用绑定 - `return` - 从函数中返回 diff --git a/src/appendix/operators.md b/src/appendix/operators.md index 6db959ac..de57084c 100644 --- a/src/appendix/operators.md +++ b/src/appendix/operators.md @@ -25,7 +25,7 @@ | `*` | `expr * expr` | 算术乘法 | `Mul` | | `*=` | `var *= expr` | 算术乘法与赋值 | `MulAssign` | | `*` | `*expr` | 解引用 | | -| `*` | `*const type`, `*mut type` | 原生指针 | | +| `*` | `*const type`, `*mut type` | 裸指针 | | | `+` | `trait + trait`, `'a + trait` | 复合类型限制 | | | `+` | `expr + expr` | 算术加法 | `Add` | | `+=` | `var += expr` | 算术加法与赋值 | `AddAssign` | diff --git a/src/async/async-await.md b/src/async-rust/async/async-await.md similarity index 100% rename from src/async/async-await.md rename to src/async-rust/async/async-await.md diff --git a/src/async/future-excuting.md b/src/async-rust/async/future-excuting.md similarity index 100% rename from src/async/future-excuting.md rename to src/async-rust/async/future-excuting.md diff --git a/src/async/getting-started.md b/src/async-rust/async/getting-started.md similarity index 100% rename from src/async/getting-started.md rename to src/async-rust/async/getting-started.md diff --git a/src/async-rust/async/intro.md b/src/async-rust/async/intro.md new file mode 100644 index 00000000..0b55ac86 --- /dev/null +++ b/src/async-rust/async/intro.md @@ -0,0 +1,5 @@ +# 异步编程 + +接下来,我们将深入了解 async/await 的使用方式及背后的原理。 + +> 本章在内容上大量借鉴和翻译了原版英文书籍[Asynchronous Programming In Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html), 特此感谢 diff --git a/src/async/multi-futures-simultaneous.md b/src/async-rust/async/multi-futures-simultaneous.md similarity index 100% rename from src/async/multi-futures-simultaneous.md rename to src/async-rust/async/multi-futures-simultaneous.md diff --git a/src/async/pain-points-and-workarounds.md b/src/async-rust/async/pain-points-and-workarounds.md similarity index 100% rename from src/async/pain-points-and-workarounds.md rename to src/async-rust/async/pain-points-and-workarounds.md diff --git a/src/async/pin-unpin.md b/src/async-rust/async/pin-unpin.md similarity index 97% rename from src/async/pin-unpin.md rename to src/async-rust/async/pin-unpin.md index c2752f6b..8932155a 100644 --- a/src/async/pin-unpin.md +++ b/src/async-rust/async/pin-unpin.md @@ -16,7 +16,7 @@ struct SelfRef { } ``` -在上面的结构体中,`pointer_to_value` 是一个原生指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若`String` 被移动了怎么办? +在上面的结构体中,`pointer_to_value` 是一个裸指针,指向第一个字段 `value` 持有的字符串 `String` 。很简单对吧?现在考虑一个情况, 若`String` 被移动了怎么办? 此时一个致命的问题就出现了:新的字符串的内存地址变了,而 `pointer_to_value` 依然指向之前的地址,一个重大 bug 就出现了! @@ -168,7 +168,7 @@ impl Test { } ``` -`Test` 提供了方法用于获取字段 `a` 和 `b` 的值的引用。这里`b` 是 `a` 的一个引用,但是我们并没有使用引用类型而是用了原生指针,原因是:Rust 的借用规则不允许我们这样用,因为不符合生命周期的要求。 此时的 `Test` 就是一个自引用结构体。 +`Test` 提供了方法用于获取字段 `a` 和 `b` 的值的引用。这里`b` 是 `a` 的一个引用,但是我们并没有使用引用类型而是用了裸指针,原因是:Rust 的借用规则不允许我们这样用,因为不符合生命周期的要求。 此时的 `Test` 就是一个自引用结构体。 如果不移动任何值,那么上面的例子将没有任何问题,例如: diff --git a/src/async/web-server.md b/src/async-rust/async/web-server.md similarity index 100% rename from src/async/web-server.md rename to src/async-rust/async/web-server.md diff --git a/src/async/intro.md b/src/async-rust/intro.md similarity index 57% rename from src/async/intro.md rename to src/async-rust/intro.md index a888f990..416c5846 100644 --- a/src/async/intro.md +++ b/src/async-rust/intro.md @@ -1,6 +1,5 @@ -# 异步编程 - -在艰难的学完 Rust 入门和进阶所有的 55 个章节后,我们终于来到了这里。假如之前攀登的是珠穆拉玛峰,那么现在攀登的就是乔戈里峰( 比珠峰还难攀爬... ),本章将学习的内容是关于 async 异步编程。 +# Rust 异步编程 +在艰难的学完 Rust 入门和进阶所有的 70 个章节后,我们终于来到了这里。假如之前攀登的是珠穆拉玛峰,那么现在攀登的就是乔戈里峰( 比珠峰还难攀爬... )。 如果你想开发 Web 服务器、数据库驱动、消息服务等需要高并发的服务,那么本章的内容将值得认真对待和学习,将从以下方面深入讲解 Rust 的异步编程: @@ -9,6 +8,4 @@ - async/await 和 Pin/Unpin - 异步编程常用的三方库 - tokio 库 -- 一些示例 - -> 本章在内容上大量借鉴和翻译了原版英文书籍[Asynchronous Programming In Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html), 特此感谢 +- 一些示例 \ No newline at end of file diff --git a/src/tokio/async.md b/src/async-rust/tokio/async.md similarity index 100% rename from src/tokio/async.md rename to src/async-rust/tokio/async.md diff --git a/src/tokio/bridging-with-sync.md b/src/async-rust/tokio/bridging-with-sync.md similarity index 100% rename from src/tokio/bridging-with-sync.md rename to src/async-rust/tokio/bridging-with-sync.md diff --git a/src/tokio/channels.md b/src/async-rust/tokio/channels.md similarity index 100% rename from src/tokio/channels.md rename to src/async-rust/tokio/channels.md diff --git a/src/tokio/frame.md b/src/async-rust/tokio/frame.md similarity index 100% rename from src/tokio/frame.md rename to src/async-rust/tokio/frame.md diff --git a/src/tokio/getting-startted.md b/src/async-rust/tokio/getting-startted.md similarity index 98% rename from src/tokio/getting-startted.md rename to src/async-rust/tokio/getting-startted.md index d2459f01..f24cb882 100644 --- a/src/tokio/getting-startted.md +++ b/src/async-rust/tokio/getting-startted.md @@ -209,7 +209,7 @@ fn main() { tokio = { version = "1", features = ["full"] } ``` -里面有个 `features = ["full"]` 可能大家会比较迷惑,当然,关于它的具体解释在本书的 [Cargo 详解专题](https://course.rs/cargo/intro.html) 有介绍,这里就简单进行说明, +里面有个 `features = ["full"]` 可能大家会比较迷惑,当然,关于它的具体解释在本书的 [Cargo 详解专题](https://course.rs/toolchains/cargo/intro.html) 有介绍,这里就简单进行说明, `Tokio` 有很多功能和特性,例如 `TCP`,`UDP`,`Unix sockets`,同步工具,多调度类型等等,不是每个应用都需要所有的这些特性。为了优化编译时间和最终生成可执行文件大小、内存占用大小,应用可以对这些特性进行可选引入。 diff --git a/src/tokio/graceful-shutdown.md b/src/async-rust/tokio/graceful-shutdown.md similarity index 100% rename from src/tokio/graceful-shutdown.md rename to src/async-rust/tokio/graceful-shutdown.md diff --git a/src/tokio/intro.md b/src/async-rust/tokio/intro.md similarity index 100% rename from src/tokio/intro.md rename to src/async-rust/tokio/intro.md diff --git a/src/tokio/io.md b/src/async-rust/tokio/io.md similarity index 100% rename from src/tokio/io.md rename to src/async-rust/tokio/io.md diff --git a/src/tokio/overview.md b/src/async-rust/tokio/overview.md similarity index 100% rename from src/tokio/overview.md rename to src/async-rust/tokio/overview.md diff --git a/src/tokio/select.md b/src/async-rust/tokio/select.md similarity index 100% rename from src/tokio/select.md rename to src/async-rust/tokio/select.md diff --git a/src/tokio/shared-state.md b/src/async-rust/tokio/shared-state.md similarity index 100% rename from src/tokio/shared-state.md rename to src/async-rust/tokio/shared-state.md diff --git a/src/tokio/spawning.md b/src/async-rust/tokio/spawning.md similarity index 100% rename from src/tokio/spawning.md rename to src/async-rust/tokio/spawning.md diff --git a/src/tokio/stream.md b/src/async-rust/tokio/stream.md similarity index 100% rename from src/tokio/stream.md rename to src/async-rust/tokio/stream.md diff --git a/src/basic/converse.md b/src/basic/converse.md index 6ff7a47c..6d2f6a0b 100644 --- a/src/basic/converse.md +++ b/src/basic/converse.md @@ -282,11 +282,11 @@ impl Clone for Container { 你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。 `mem::transmute_copy` 才是真正的深渊,它比之前的还要更加危险和不安全。它从 `T` 类型中拷贝出 `U` 类型所需的字节数,然后转换成 `U`。 `mem::transmute` 尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过 `U` 的尺寸若是比 `T` 大,会是一个未定义行为。 -当然,你也可以通过原生指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。 +当然,你也可以通过裸指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。裸指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。 `transmute` 虽然危险,但作为一本工具书,知识当然要全面,下面列举两个有用的 `transmute` 应用场景 :)。 -- 将原生指针变成函数指针: +- 将裸指针变成函数指针: ```rust fn foo() -> i32 { @@ -295,7 +295,7 @@ fn foo() -> i32 { let pointer = foo as *const (); let function = unsafe { - // 将原生指针转换为函数指针 + // 将裸指针转换为函数指针 std::mem::transmute::<*const (), fn() -> i32>(pointer) }; assert_eq!(function(), 0); diff --git a/src/confonding/pointer.md b/src/confonding/pointer.md deleted file mode 100644 index 146132f8..00000000 --- a/src/confonding/pointer.md +++ /dev/null @@ -1 +0,0 @@ -# 原生指针、引用和智能指针 todo diff --git a/src/errorindex/borrowing/intro.md b/src/errorindex/borrowing/intro.md deleted file mode 100644 index fcc4d744..00000000 --- a/src/errorindex/borrowing/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 所有权和借用 diff --git a/src/errorindex/intro.md b/src/errorindex/intro.md deleted file mode 100644 index 7258196c..00000000 --- a/src/errorindex/intro.md +++ /dev/null @@ -1,3 +0,0 @@ -# 复杂错误索引 - -读者可以在本章中通过错误前缀来索引查询相应的解决方案,简单的错误并不在本章的内容范畴之内。 \ No newline at end of file diff --git a/src/errorindex/lifetime/intro.md b/src/errorindex/lifetime/intro.md deleted file mode 100644 index b859823e..00000000 --- a/src/errorindex/lifetime/intro.md +++ /dev/null @@ -1 +0,0 @@ -# 生命周期 todo diff --git a/src/first-try/cargo.md b/src/first-try/cargo.md index a008da22..1592a553 100644 --- a/src/first-try/cargo.md +++ b/src/first-try/cargo.md @@ -161,10 +161,10 @@ color = { git = "https://github.com/bjz/color-rs" } geometry = { path = "crates/geometry" } ``` -相信聪明的读者已经能看懂该如何引入外部依赖库,这里就不再赘述。详细的说明参见此章:[Cargo 依赖管理](https://course.rs/cargo/reference/specify-deps.html),但是不建议大家现在去看,只要按照目录浏览,拨云见雾指日可待。 +相信聪明的读者已经能看懂该如何引入外部依赖库,这里就不再赘述。详细的说明参见此章:[Cargo 依赖管理](https://course.rs/toolchains/cargo/reference/specify-deps.html),但是不建议大家现在去看,只要按照目录浏览,拨云见雾指日可待。 ## 基于 cargo 的项目组织结构 -前文有提到 `cargo` 默认生成的项目结构,真实的项目肯定会有所不同,但是在目前的学习阶段,还无需关注。感兴趣的同学可以移步:[Cargo 项目结构](https://course.rs/cargo/guide/package-layout.html ) +前文有提到 `cargo` 默认生成的项目结构,真实的项目肯定会有所不同,但是在目前的学习阶段,还无需关注。感兴趣的同学可以移步:[Cargo 项目结构](https://course.rs/toolchains/cargo/guide/package-layout.html) 至此,大家对 Rust 项目的创建和管理已经有了初步的了解,那么来完善刚才的`"世界,你好"`项目吧。 diff --git a/src/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md b/src/practice/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md similarity index 100% rename from src/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md rename to src/practice/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md diff --git a/src/fight-with-compiler/borrowing/intro.md b/src/practice/fight-with-compiler/borrowing/intro.md similarity index 100% rename from src/fight-with-compiler/borrowing/intro.md rename to src/practice/fight-with-compiler/borrowing/intro.md diff --git a/src/fight-with-compiler/borrowing/ref-exist-in-out-fn.md b/src/practice/fight-with-compiler/borrowing/ref-exist-in-out-fn.md similarity index 100% rename from src/fight-with-compiler/borrowing/ref-exist-in-out-fn.md rename to src/practice/fight-with-compiler/borrowing/ref-exist-in-out-fn.md diff --git a/src/fight-with-compiler/intro.md b/src/practice/fight-with-compiler/intro.md similarity index 100% rename from src/fight-with-compiler/intro.md rename to src/practice/fight-with-compiler/intro.md diff --git a/src/fight-with-compiler/lifetime/closure-with-static.md b/src/practice/fight-with-compiler/lifetime/closure-with-static.md similarity index 100% rename from src/fight-with-compiler/lifetime/closure-with-static.md rename to src/practice/fight-with-compiler/lifetime/closure-with-static.md diff --git a/src/fight-with-compiler/lifetime/intro.md b/src/practice/fight-with-compiler/lifetime/intro.md similarity index 100% rename from src/fight-with-compiler/lifetime/intro.md rename to src/practice/fight-with-compiler/lifetime/intro.md diff --git a/src/fight-with-compiler/lifetime/loop.md b/src/practice/fight-with-compiler/lifetime/loop.md similarity index 100% rename from src/fight-with-compiler/lifetime/loop.md rename to src/practice/fight-with-compiler/lifetime/loop.md diff --git a/src/fight-with-compiler/lifetime/too-long1.md b/src/practice/fight-with-compiler/lifetime/too-long1.md similarity index 100% rename from src/fight-with-compiler/lifetime/too-long1.md rename to src/practice/fight-with-compiler/lifetime/too-long1.md diff --git a/src/fight-with-compiler/lifetime/too-long2.md b/src/practice/fight-with-compiler/lifetime/too-long2.md similarity index 100% rename from src/fight-with-compiler/lifetime/too-long2.md rename to src/practice/fight-with-compiler/lifetime/too-long2.md diff --git a/src/fight-with-compiler/phantom-data.md b/src/practice/fight-with-compiler/phantom-data.md similarity index 100% rename from src/fight-with-compiler/phantom-data.md rename to src/practice/fight-with-compiler/phantom-data.md diff --git a/src/fight-with-compiler/unconstrained.md b/src/practice/fight-with-compiler/unconstrained.md similarity index 100% rename from src/fight-with-compiler/unconstrained.md rename to src/practice/fight-with-compiler/unconstrained.md diff --git a/src/practice/intro.md b/src/practice/intro.md index 7f89d0ee..805221d7 100644 --- a/src/practice/intro.md +++ b/src/practice/intro.md @@ -1,2 +1,3 @@ # Rust最佳实践 + 对于生产级项目而言,运行稳定性和可维护性是非常重要的,本章就一起来看看 Rust 项目有哪些最佳实践准则。 \ No newline at end of file diff --git a/src/pitfalls/arithmetic-overflow.md b/src/practice/pitfalls/arithmetic-overflow.md similarity index 100% rename from src/pitfalls/arithmetic-overflow.md rename to src/practice/pitfalls/arithmetic-overflow.md diff --git a/src/pitfalls/closure-with-lifetime.md b/src/practice/pitfalls/closure-with-lifetime.md similarity index 100% rename from src/pitfalls/closure-with-lifetime.md rename to src/practice/pitfalls/closure-with-lifetime.md diff --git a/src/pitfalls/index.md b/src/practice/pitfalls/index.md similarity index 100% rename from src/pitfalls/index.md rename to src/practice/pitfalls/index.md diff --git a/src/pitfalls/iterator-everywhere.md b/src/practice/pitfalls/iterator-everywhere.md similarity index 100% rename from src/pitfalls/iterator-everywhere.md rename to src/practice/pitfalls/iterator-everywhere.md diff --git a/src/pitfalls/lazy-iterators.md b/src/practice/pitfalls/lazy-iterators.md similarity index 100% rename from src/pitfalls/lazy-iterators.md rename to src/practice/pitfalls/lazy-iterators.md diff --git a/src/pitfalls/main-with-channel-blocked.md b/src/practice/pitfalls/main-with-channel-blocked.md similarity index 100% rename from src/pitfalls/main-with-channel-blocked.md rename to src/practice/pitfalls/main-with-channel-blocked.md diff --git a/src/pitfalls/multiple-mutable-references.md b/src/practice/pitfalls/multiple-mutable-references.md similarity index 100% rename from src/pitfalls/multiple-mutable-references.md rename to src/practice/pitfalls/multiple-mutable-references.md diff --git a/src/pitfalls/stack-overflow.md b/src/practice/pitfalls/stack-overflow.md similarity index 100% rename from src/pitfalls/stack-overflow.md rename to src/practice/pitfalls/stack-overflow.md diff --git a/src/pitfalls/the-disabled-mutability.md b/src/practice/pitfalls/the-disabled-mutability.md similarity index 100% rename from src/pitfalls/the-disabled-mutability.md rename to src/practice/pitfalls/the-disabled-mutability.md diff --git a/src/pitfalls/use-vec-in-for.md b/src/practice/pitfalls/use-vec-in-for.md similarity index 100% rename from src/pitfalls/use-vec-in-for.md rename to src/practice/pitfalls/use-vec-in-for.md diff --git a/src/pitfalls/utf8-performance.md b/src/practice/pitfalls/utf8-performance.md similarity index 100% rename from src/pitfalls/utf8-performance.md rename to src/practice/pitfalls/utf8-performance.md diff --git a/src/pitfalls/weird-ranges.md b/src/practice/pitfalls/weird-ranges.md similarity index 100% rename from src/pitfalls/weird-ranges.md rename to src/practice/pitfalls/weird-ranges.md diff --git a/src/test/unit-integration-test.md b/src/test/unit-integration-test.md index 3d7e6636..a639d251 100644 --- a/src/test/unit-integration-test.md +++ b/src/test/unit-integration-test.md @@ -37,7 +37,7 @@ mod tests { 在 `#[cfg(test)]` 中,`cfg` 是配置 `configuration` 的缩写,它告诉 Rust :当 `test` 配置项存在时,才运行下面的代码,而 `cargo test` 在运行时,就会将 `test` 这个配置项传入进来,因此后面的 `tests` 模块会被包含进来。 -大家看出来了吗?这是典型的条件编译,`Cargo` 会根据指定的配置来选择是否编译指定的代码,事实上关于条件编译 Rust 能做的不仅仅是这些,在 [`Cargo` 专题](https://course.rs/cargo/intro.html)中我们会进行更为详细的介绍。 +大家看出来了吗?这是典型的条件编译,`Cargo` 会根据指定的配置来选择是否编译指定的代码,事实上关于条件编译 Rust 能做的不仅仅是这些,在 [`Cargo` 专题](https://course.rs/toolchains/cargo/intro.html)中我们会进行更为详细的介绍。 #### 测试私有函数 diff --git a/src/too-many-lists/deque/final-code.md b/src/too-many-lists/deque/final-code.md new file mode 100644 index 00000000..3547589d --- /dev/null +++ b/src/too-many-lists/deque/final-code.md @@ -0,0 +1,247 @@ +# 最终代码 +这一章真不好写( 也很难翻译... ),最终我们实现了一个 100% 安全但是功能残缺的双向链表。 + +同时在实现中,还有大量 `Rc` 和 `RefCell` 引起的运行时检查,最终会影响链表的性能。整个双向链表实现史就是一部别名和所有权的奋斗史。 + +总之,不管爱与不爱,它就这样了,特别是如果我们不在意内部的细节暴露给外面用户时。 + +而从下一章开始,我们将实现一个真正能够全盘掌控的链表,当然...通过 unsafe 代码实现! + + +```rust + +#![allow(unused)] +fn main() { +use std::rc::Rc; +use std::cell::{Ref, RefMut, RefCell}; + +pub struct List { + head: Link, + tail: Link, +} + +type Link = Option>>>; + +struct Node { + elem: T, + next: Link, + prev: Link, +} + + +impl Node { + fn new(elem: T) -> Rc> { + Rc::new(RefCell::new(Node { + elem: elem, + prev: None, + next: None, + })) + } +} + +impl List { + pub fn new() -> Self { + List { head: None, tail: None } + } + + pub fn push_front(&mut self, elem: T) { + let new_head = Node::new(elem); + match self.head.take() { + Some(old_head) => { + old_head.borrow_mut().prev = Some(new_head.clone()); + new_head.borrow_mut().next = Some(old_head); + self.head = Some(new_head); + } + None => { + self.tail = Some(new_head.clone()); + self.head = Some(new_head); + } + } + } + + pub fn push_back(&mut self, elem: T) { + let new_tail = Node::new(elem); + match self.tail.take() { + Some(old_tail) => { + old_tail.borrow_mut().next = Some(new_tail.clone()); + new_tail.borrow_mut().prev = Some(old_tail); + self.tail = Some(new_tail); + } + None => { + self.head = Some(new_tail.clone()); + self.tail = Some(new_tail); + } + } + } + + pub fn pop_back(&mut self) -> Option { + self.tail.take().map(|old_tail| { + match old_tail.borrow_mut().prev.take() { + Some(new_tail) => { + new_tail.borrow_mut().next.take(); + self.tail = Some(new_tail); + } + None => { + self.head.take(); + } + } + Rc::try_unwrap(old_tail).ok().unwrap().into_inner().elem + }) + } + + pub fn pop_front(&mut self) -> Option { + self.head.take().map(|old_head| { + match old_head.borrow_mut().next.take() { + Some(new_head) => { + new_head.borrow_mut().prev.take(); + self.head = Some(new_head); + } + None => { + self.tail.take(); + } + } + Rc::try_unwrap(old_head).ok().unwrap().into_inner().elem + }) + } + + pub fn peek_front(&self) -> Option> { + self.head.as_ref().map(|node| { + Ref::map(node.borrow(), |node| &node.elem) + }) + } + + pub fn peek_back(&self) -> Option> { + self.tail.as_ref().map(|node| { + Ref::map(node.borrow(), |node| &node.elem) + }) + } + + pub fn peek_back_mut(&mut self) -> Option> { + self.tail.as_ref().map(|node| { + RefMut::map(node.borrow_mut(), |node| &mut node.elem) + }) + } + + pub fn peek_front_mut(&mut self) -> Option> { + self.head.as_ref().map(|node| { + RefMut::map(node.borrow_mut(), |node| &mut node.elem) + }) + } + + pub fn into_iter(self) -> IntoIter { + IntoIter(self) + } +} + +impl Drop for List { + fn drop(&mut self) { + while self.pop_front().is_some() {} + } +} + +pub struct IntoIter(List); + +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + self.0.pop_front() + } +} + +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.0.pop_back() + } +} + +#[cfg(test)] +mod test { + use super::List; + + #[test] + fn basics() { + let mut list = List::new(); + + // Check empty list behaves right + assert_eq!(list.pop_front(), None); + + // Populate list + list.push_front(1); + list.push_front(2); + list.push_front(3); + + // Check normal removal + assert_eq!(list.pop_front(), Some(3)); + assert_eq!(list.pop_front(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push_front(4); + list.push_front(5); + + // Check normal removal + assert_eq!(list.pop_front(), Some(5)); + assert_eq!(list.pop_front(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop_front(), Some(1)); + assert_eq!(list.pop_front(), None); + + // ---- back ----- + + // Check empty list behaves right + assert_eq!(list.pop_back(), None); + + // Populate list + list.push_back(1); + list.push_back(2); + list.push_back(3); + + // Check normal removal + assert_eq!(list.pop_back(), Some(3)); + assert_eq!(list.pop_back(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push_back(4); + list.push_back(5); + + // Check normal removal + assert_eq!(list.pop_back(), Some(5)); + assert_eq!(list.pop_back(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop_back(), Some(1)); + assert_eq!(list.pop_back(), None); + } + + #[test] + fn peek() { + let mut list = List::new(); + assert!(list.peek_front().is_none()); + assert!(list.peek_back().is_none()); + assert!(list.peek_front_mut().is_none()); + assert!(list.peek_back_mut().is_none()); + + list.push_front(1); list.push_front(2); list.push_front(3); + + assert_eq!(&*list.peek_front().unwrap(), &3); + assert_eq!(&mut *list.peek_front_mut().unwrap(), &mut 3); + assert_eq!(&*list.peek_back().unwrap(), &1); + assert_eq!(&mut *list.peek_back_mut().unwrap(), &mut 1); + } + + #[test] + fn into_iter() { + let mut list = List::new(); + list.push_front(1); list.push_front(2); list.push_front(3); + + let mut iter = list.into_iter(); + assert_eq!(iter.next(), Some(3)); + assert_eq!(iter.next_back(), Some(1)); + assert_eq!(iter.next(), Some(2)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next(), None); + } +} +} +``` \ No newline at end of file diff --git a/src/too-many-lists/deque/iterator.md b/src/too-many-lists/deque/iterator.md new file mode 100644 index 00000000..23be7d6f --- /dev/null +++ b/src/too-many-lists/deque/iterator.md @@ -0,0 +1,238 @@ +# 迭代器 +坏男孩最令人头疼,而链表实现中,迭代器就是这样的坏男孩,所以我们放在最后来处理。 + +## IntoIter +由于是转移所有权,因此 `IntoIter` 一直都是最好实现的: +```rust +pub struct IntoIter(List); + +impl List { + pub fn into_iter(self) -> IntoIter { + IntoIter(self) + } +} + +impl Iterator for IntoIter { + type Item = T; + fn next(&mut self) -> Option { + self.0.pop_front() + } +} +``` + +但是关于双向链表,有一个有趣的事实,它不仅可以从前向后迭代,还能反过来。前面实现的是传统的从前到后,那问题来了,反过来该如何实现呢? + +答案是: `DoubleEndedIterator`,它继承自 `Iterator`( 通过 [`supertrait`](https://course.rs/basic/trait/advance-trait.html?highlight=supertrait#特征定义中的特征约束) ),因此意味着要实现该特征,首先需要实现 `Iterator`。 + +这样只要为 `DoubleEndedIterator` 实现 `next_back` 方法,就可以支持双向迭代了: `Iterator` 的 `next` 方法从前往后,而 `next_back` 从后向前。 + +```rust +impl DoubleEndedIterator for IntoIter { + fn next_back(&mut self) -> Option { + self.0.pop_back() + } +} +``` + +测试下: +```rust +#[test] +fn into_iter() { + let mut list = List::new(); + list.push_front(1); list.push_front(2); list.push_front(3); + + let mut iter = list.into_iter(); + assert_eq!(iter.next(), Some(3)); + assert_eq!(iter.next_back(), Some(1)); + assert_eq!(iter.next(), Some(2)); + assert_eq!(iter.next_back(), None); + assert_eq!(iter.next(), None); +} +``` + +```shell +cargo test + + Running target/debug/lists-5c71138492ad4b4a + +running 11 tests +test fourth::test::basics ... ok +test fourth::test::peek ... ok +test fourth::test::into_iter ... ok +test first::test::basics ... ok +test second::test::basics ... ok +test second::test::iter ... ok +test second::test::iter_mut ... ok +test third::test::iter ... ok +test third::test::basics ... ok +test second::test::into_iter ... ok +test second::test::peek ... ok + +test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured +``` + +## Iter +这里又要用到糟糕的 `Ref`: +```rust +pub struct Iter<'a, T>(Option>>); + +impl List { + pub fn iter(&self) -> Iter { + Iter(self.head.as_ref().map(|head| head.borrow())) + } +} +``` + +```shell +> cargo build +``` + +迄今为止一切运行正常,接下来的 `next` 实现起来会有些麻烦: +```rust +impl<'a, T> Iterator for Iter<'a, T> { + type Item = Ref<'a, T>; + fn next(&mut self) -> Option { + self.0.take().map(|node_ref| { + self.0 = node_ref.next.as_ref().map(|head| head.borrow()); + Ref::map(node_ref, |node| &node.elem) + }) + } +} +``` + +```shell +cargo build + +error[E0521]: borrowed data escapes outside of closure + --> src/fourth.rs:155:13 + | +153 | fn next(&mut self) -> Option { + | --------- `self` is declared here, outside of the closure body +154 | self.0.take().map(|node_ref| { +155 | self.0 = node_ref.next.as_ref().map(|head| head.borrow()); + | ^^^^^^ -------- borrow is only valid in the closure body + | | + | reference to `node_ref` escapes the closure body here + +error[E0505]: cannot move out of `node_ref` because it is borrowed + --> src/fourth.rs:156:22 + | +153 | fn next(&mut self) -> Option { + | --------- lifetime `'1` appears in the type of `self` +154 | self.0.take().map(|node_ref| { +155 | self.0 = node_ref.next.as_ref().map(|head| head.borrow()); + | ------ -------- borrow of `node_ref` occurs here + | | + | assignment requires that `node_ref` is borrowed for `'1` +156 | Ref::map(node_ref, |node| &node.elem) + | ^^^^^^^^ move out of `node_ref` occurs here +``` + +果然,膝盖又中了一箭。 + +`node_ref` 活得不够久,跟一般的引用不同,Rust 不允许我们这样分割 `Ref`,从 `head.borrow()` 中取出的 `Ref` 只允许跟 `node_ref` 活得一样久。 + + +而我们想要的函数是存在的: +```rust +pub fn map_split(orig: Ref<'b, T>, f: F) -> (Ref<'b, U>, Ref<'b, V>) where + F: FnOnce(&T) -> (&U, &V), + U: ?Sized, + V: ?Sized, +``` + +喔,这个函数定义的泛型直接晃瞎了我的眼睛。。 +```rust +fn next(&mut self) -> Option { + self.0.take().map(|node_ref| { + let (next, elem) = Ref::map_split(node_ref, |node| { + (&node.next, &node.elem) + }); + + self.0 = next.as_ref().map(|head| head.borrow()); + + elem + }) +} +``` + +```shell +cargo build + Compiling lists v0.1.0 (/Users/ABeingessner/dev/temp/lists) +error[E0521]: borrowed data escapes outside of closure + --> src/fourth.rs:159:13 + | +153 | fn next(&mut self) -> Option { + | --------- `self` is declared here, outside of the closure body +... +159 | self.0 = next.as_ref().map(|head| head.borrow()); + | ^^^^^^ ---- borrow is only valid in the closure body + | | + | reference to `next` escapes the closure body here +``` + +额,借用的内容只允许在闭包体中使用,看起来我们还是得用 `Ref::map` 来解决问题: +```rust +fn next(&mut self) -> Option { + self.0.take().map(|node_ref| { + let (next, elem) = Ref::map_split(node_ref, |node| { + (&node.next, &node.elem) + }); + + self.0 = if next.is_some() { + Some(Ref::map(next, |next| &**next.as_ref().unwrap())) + } else { + None + }; + + elem + }) +} +``` + +```shell +error[E0308]: mismatched types + --> src/fourth.rs:162:22 + | +162 | Some(Ref::map(next, |next| &**next.as_ref().unwrap())) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `fourth::Node`, found struct `std::cell::RefCell` + | + = note: expected type `std::cell::Ref<'_, fourth::Node<_>>` + found type `std::cell::Ref<'_, std::cell::RefCell>>` +``` + +晕, 多了一个 `RefCell` ,随着我们的对链表的逐步深入,`RefCell` 的代码嵌套变成了不可忽视的问题。 + +看起来我们已经无能为力了,只能试着去摆脱 `RefCell` 了。`Rc` 怎么样?我们完全可以对 `Rc` 进行完整的克隆: +```rust +pub struct Iter(Option>>); + +impl List { + pub fn iter(&self) -> Iter { + Iter(self.head.as_ref().map(|head| head.clone())) + } +} + +impl Iterator for Iter { + type Item = +``` + +等等,那现在返回的是什么?`&T` 还是 `Ref` ? + +两者都不是,现在我们的 `Iter` 已经没有生命周期了:无论是 `&T` 还是 `Ref` 都需要我们在 `next` 之前声明好生命周期。但是我们试图从 `Rc` 中取出来的值其实是迭代器的引用。 + +也可以通过对 `Rc` 进行 map 获取到 `Rc`?但是标准库并没有给我们提供相应的功能,第三方倒是有[一个](https://crates.io/crates/owning_ref)。 + +但是,即使这么做了,还有一个更大的坑在等着:一个会造成迭代器不合法的可怕幽灵。事实上,之前我们对于迭代器不合法是免疫的,但是一旦迭代器产生 `Rc`,那它们就不再会借用链表。这意味着人们可以在持有指向链表内部的指针时,还可以进行 `push` 和 `pop` 操作。 + +严格来说,`push` 问题不大,因为链表两端的增长不会对我们正在关注的某个子链表造成影响。 + +但是 `pop` 就是另一个故事了,如果在我们关注的子链表之外 `pop`, 那问题不大。但是如果是 `pop` 一个正在引用的子链表中的节点呢?那一切就完了,特别是,如果大家还试图去 unwrap `try_unwrap` 返回的 `Result` ,会直接造成整个程序的 `panic`。 + +仔细想一想,好像也不错,程序一切正常,除非去 `pop` 我们正在引用的节点,最美的是,就算遇到这种情况,程序也会直接崩溃,提示我们错误的发生。 + +其实我们大部分的努力都是为了实现隐藏的细节和优雅的 API,典型的二八原则,八成时间花在二成的细节上。但是如果不关心这些细节,可以接受自己的平凡的话,那把节点简单的到处传递就行。 + +总之,可以看出,内部可变性非常适合写一个安全性的应用程序,但是如果是安全性高的库,那内部可变性就有些捉襟见肘了。 + +最终,我选择了放弃,不再实现 `Iter` 和 `IterMut`,也许努力下,可以实现,但是。。。不愉快,算了。 \ No newline at end of file diff --git a/src/too-many-lists/deque/peek.md b/src/too-many-lists/deque/peek.md new file mode 100644 index 00000000..f9fda431 --- /dev/null +++ b/src/too-many-lists/deque/peek.md @@ -0,0 +1,136 @@ +# Peek +`push` 和 `pop` 的防不胜防的编译报错着实让人出了些冷汗,下面来看看轻松的,至少在之前的链表中是很轻松的 :) + +```rust +pub fn peek_front(&self) -> Option<&T> { + self.head.as_ref().map(|node| { + &node.elem + }) +} +``` + +额...好像被人发现我是复制黏贴的了,赶紧换一个: +```rust +pub fn peek_front(&self) -> Option<&T> { + self.head.as_ref().map(|node| { + // BORROW!!!! + &node.borrow().elem + }) +} +``` + +```shell +cargo build + +error[E0515]: cannot return value referencing temporary value + --> src/fourth.rs:66:13 + | +66 | &node.borrow().elem + | ^ ----------^^^^^ + | | | + | | temporary value created here + | | + | returns a value referencing data owned by the current function +``` + +从报错可以看出,原因是我们引用了局部的变量并试图在函数中返回。为了解释这个问题,先来看看 `borrow` 的定义: +```rust +fn borrow<'a>(&'a self) -> Ref<'a, T> +fn borrow_mut<'a>(&'a self) -> RefMut<'a, T> +``` + +这里返回的并不是 `&T` 或 `&mut T`,而是一个 [`Ref`](https://doc.rust-lang.org/std/cell/struct.Ref.html) 和 [`RefMut`](https://doc.rust-lang.org/std/cell/struct.RefMut.html),那么它们是什么?说白了,它们就是在借用到的引用外包裹了一层。而且 `Ref` 和 `RefMut` 分别实现了 `Deref` 和 `DerefMut`,在绝大多数场景中,我们都可以像使用 `&T` 一样去使用它们。 + + +只能说是成是败都赖萧何,恰恰就因为这一层包裹,导致生命周期改变了,也就是 `Ref` 和内部引用的生命周期不再和 `RefCell` 相同,而 `Ref` 的生命周期是什么,相信大家都能看得出来,因此就造成了局部引用的问题。 + +事实上,这是必须的,如果内部的引用和外部的 `Ref` 生命周期不一致,那该如何管理?当 `Ref` 因超出作用域被 `drop` 时,内部的引用怎么办? + +现在该怎么办?我们只想要一个引用,现在却多了一个 `Ref` 拦路虎。等等,如果我们不返回 `&T` 而是返回 `Ref` 呢? +```rust +use std::cell::{Ref, RefCell}; + +pub fn peek_front(&self) -> Option> { + self.head.as_ref().map(|node| { + node.borrow() + }) +} +``` + +```shell +> cargo build + +error[E0308]: mismatched types + --> src/fourth.rs:64:9 + | +64 | / self.head.as_ref().map(|node| { +65 | | node.borrow() +66 | | }) + | |__________^ expected type parameter, found struct `fourth::Node` + | + = note: expected type `std::option::Option>` + found type `std::option::Option>>` +``` + +嗯,类型不匹配了,要返回的是 `Ref` 但是获取的却是 `Ref>`,那么现在看上去有两个选择: + +- 抛弃这条路,换一条重新开始 +- 一条路走到死,最终通过更复杂的实现来解决 + +但是,仔细想想,这两个选择都不是我们想要的,那没办法了,只能继续深挖,看看有没有其它解决办法。啊哦,还真发现了一只野兽: +```rust +map(orig: Ref<'b, T>, f: F) -> Ref<'b, U> + where F: FnOnce(&T) -> &U, + U: ?Sized +``` + +就像在 `Result` 和 `Option` 上使用 `map` 一样,我们还能在 `Ref` 上使用 `map`: +```rust +pub fn peek_front(&self) -> Option> { + self.head.as_ref().map(|node| { + Ref::map(node.borrow(), |node| &node.elem) + }) +} +``` + +```shell +> cargo build +``` + +Gooood! 本章节的编译错误可以说是多个链表中最难解决的之一,依然被我们成功搞定了! + + +下面来写下测试用例,需要注意的是 `Ref` 不能被直接比较,因此我们需要先利用 `Deref` 解引用出其中的值,再进行比较。 + +```rust +#[test] +fn peek() { + let mut list = List::new(); + assert!(list.peek_front().is_none()); + list.push_front(1); list.push_front(2); list.push_front(3); + + assert_eq!(&*list.peek_front().unwrap(), &3); +} +``` + +```shell +> cargo test + + Running target/debug/lists-5c71138492ad4b4a + +running 10 tests +test first::test::basics ... ok +test fourth::test::basics ... ok +test second::test::basics ... ok +test fourth::test::peek ... ok +test second::test::iter_mut ... ok +test second::test::into_iter ... ok +test third::test::basics ... ok +test second::test::peek ... ok +test second::test::iter ... ok +test third::test::iter ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured +``` + +终于可以把文章开头的冷汗擦拭干净了,忘掉这个章节吧,让我来养你...哦不对,让我们开始一段真正轻松的章节。 \ No newline at end of file diff --git a/src/too-many-lists/deque/symmetric.md b/src/too-many-lists/deque/symmetric.md new file mode 100644 index 00000000..c97584c7 --- /dev/null +++ b/src/too-many-lists/deque/symmetric.md @@ -0,0 +1,161 @@ +# 基本操作的对称镜像 +之前我们仅实现了头部的 `push`、`pop` ,现在来补全一下,大自然的对称之美咱的双向链表也不能少了。 + +```rust +tail <-> head +next <-> prev +front -> back +``` + +需要注意的是,这里还新增了 `mut` 类型的 peek: +```rust +use std::cell::{Ref, RefCell, RefMut}; + +//.. + +pub fn push_back(&mut self, elem: T) { + let new_tail = Node::new(elem); + match self.tail.take() { + Some(old_tail) => { + old_tail.borrow_mut().next = Some(new_tail.clone()); + new_tail.borrow_mut().prev = Some(old_tail); + self.tail = Some(new_tail); + } + None => { + self.head = Some(new_tail.clone()); + self.tail = Some(new_tail); + } + } +} + +pub fn pop_back(&mut self) -> Option { + self.tail.take().map(|old_tail| { + match old_tail.borrow_mut().prev.take() { + Some(new_tail) => { + new_tail.borrow_mut().next.take(); + self.tail = Some(new_tail); + } + None => { + self.head.take(); + } + } + Rc::try_unwrap(old_tail).ok().unwrap().into_inner().elem + }) +} + +pub fn peek_back(&self) -> Option> { + self.tail.as_ref().map(|node| { + Ref::map(node.borrow(), |node| &node.elem) + }) +} + +pub fn peek_back_mut(&mut self) -> Option> { + self.tail.as_ref().map(|node| { + RefMut::map(node.borrow_mut(), |node| &mut node.elem) + }) +} + +pub fn peek_front_mut(&mut self) -> Option> { + self.head.as_ref().map(|node| { + RefMut::map(node.borrow_mut(), |node| &mut node.elem) + }) +} +``` + +再更新测试用例: +```rust +#[test] +fn basics() { + let mut list = List::new(); + + // Check empty list behaves right + assert_eq!(list.pop_front(), None); + + // Populate list + list.push_front(1); + list.push_front(2); + list.push_front(3); + + // Check normal removal + assert_eq!(list.pop_front(), Some(3)); + assert_eq!(list.pop_front(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push_front(4); + list.push_front(5); + + // Check normal removal + assert_eq!(list.pop_front(), Some(5)); + assert_eq!(list.pop_front(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop_front(), Some(1)); + assert_eq!(list.pop_front(), None); + + // ---- back ----- + + // Check empty list behaves right + assert_eq!(list.pop_back(), None); + + // Populate list + list.push_back(1); + list.push_back(2); + list.push_back(3); + + // Check normal removal + assert_eq!(list.pop_back(), Some(3)); + assert_eq!(list.pop_back(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push_back(4); + list.push_back(5); + + // Check normal removal + assert_eq!(list.pop_back(), Some(5)); + assert_eq!(list.pop_back(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop_back(), Some(1)); + assert_eq!(list.pop_back(), None); +} + +#[test] +fn peek() { + let mut list = List::new(); + assert!(list.peek_front().is_none()); + assert!(list.peek_back().is_none()); + assert!(list.peek_front_mut().is_none()); + assert!(list.peek_back_mut().is_none()); + + list.push_front(1); list.push_front(2); list.push_front(3); + + assert_eq!(&*list.peek_front().unwrap(), &3); + assert_eq!(&mut *list.peek_front_mut().unwrap(), &mut 3); + assert_eq!(&*list.peek_back().unwrap(), &1); + assert_eq!(&mut *list.peek_back_mut().unwrap(), &mut 1); +} +``` + +什么?你问我这里的测试用例全吗?只能说如果测试全部的组合情况,这一章节会被撑爆。至于现在,能不出错就谢天谢地了 :( + +```shell +> cargo test + + Running target/debug/lists-5c71138492ad4b4a + +running 10 tests +test first::test::basics ... ok +test fourth::test::basics ... ok +test second::test::basics ... ok +test fourth::test::peek ... ok +test second::test::iter ... ok +test third::test::iter ... ok +test second::test::into_iter ... ok +test second::test::iter_mut ... ok +test second::test::peek ... ok +test third::test::basics ... ok + +test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured +``` + +我想说:Ctrl CV 是最好的编程工具,大家同意吗? \ No newline at end of file diff --git a/src/too-many-lists/unsafe-queue/basics.md b/src/too-many-lists/unsafe-queue/basics.md new file mode 100644 index 00000000..3a3c17a4 --- /dev/null +++ b/src/too-many-lists/unsafe-queue/basics.md @@ -0,0 +1,246 @@ +# 基本操作 + +> 本章节的代码中有一个隐藏的 bug,因为它藏身于 unsafe 中,因此不会导致报错,我们会在后续章节解决这个问题,所以,请不要在生产环境使用此处的代码 + +在开始之前,大家需要先了解 unsafe 的[相关知识](https://course.rs/advance/unsafe/intro.html)。那么,言归正传,该如何构建一个链表?在之前我们是这么做的: +```rust +impl List { + pub fn new() -> Self { + List { head: None, tail: None } + } +} +``` + +但是我们不再在 `tail` 中使用 `Option`: +```shell +> cargo build + +error[E0308]: mismatched types + --> src/fifth.rs:15:34 + | +15 | List { head: None, tail: None } + | ^^^^ expected *-ptr, found + | enum `std::option::Option` + | + = note: expected type `*mut fifth::Node` + found type `std::option::Option<_>` +``` + +我们是可以使用 `Option` 包裹一层,但是 `*mut` 裸指针之所以裸,是因为它狂,它可以是 `null` ! 因此 `Option` 就变得没有意义: +```rust +use std::ptr; + +// defns... + +impl List { + pub fn new() -> Self { + List { head: None, tail: ptr::null_mut() } + } +} +``` + +如上所示,通过 `std::ptr::null_mut` 函数可以获取一个 `null`,当然,还可以使用 `0 as *mut _`,但是...已经这么不安全了,好歹我们要留一点代码可读性上的尊严吧 = , = + +好了,现在是时候去重新实现 `push` ,之前获取的是 `Option<&mut Node>` 成为我们的拦路虎,这次来看看如果是获取 `*mut Node` 还会不会有类似的问题。 + +首先,该如何将一个普通的引用变成裸指针?答案是:强制转换 Coercions。 + +```rust +let raw_tail: *mut _ = &mut *new_tail; +``` + +来看看 `push` 的实现: +```rust +pub fn push(&mut self, elem: T) { + let mut new_tail = Box::new(Node { + elem: elem, + next: None, + }); + + let raw_tail: *mut _ = &mut *new_tail; + + // .is_null 会检查是否为 null, 在功能上等价于 `None` 的检查 + if !self.tail.is_null() { + // 如果 old tail 存在,那将其指向新的 tail + self.tail.next = Some(new_tail); + } else { + // 否则让 head 指向新的 tail + self.head = Some(new_tail); + } + + self.tail = raw_tail; +} +``` + +```shell +> cargo build + +error[E0609]: no field `next` on type `*mut fifth::Node` + --> src/fifth.rs:31:23 + | +31 | self.tail.next = Some(new_tail); + | ----------^^^^ + | | + | help: `self.tail` is a raw pointer; + | try dereferencing it: `(*self.tail).next` +``` + +当使用裸指针时,一些 Rust 提供的便利条件也将不复存在,例如由于不安全性的存在,裸指针需要我们手动去解引用( deref ): +```rust +*self.tail.next = Some(new_tail); +``` + +```shell +> cargo build + +error[E0609]: no field `next` on type `*mut fifth::Node` + --> src/fifth.rs:31:23 + | +31 | *self.tail.next = Some(new_tail); + | -----------^^^^ + | | + | help: `self.tail` is a raw pointer; + | try dereferencing it: `(*self.tail).next` +``` + +哦哦,运算符的优先级问题: +```rust +(*self.tail).next = Some(new_tail); +``` + +```shell +> cargo build + +error[E0133]: dereference of raw pointer is unsafe and requires + unsafe function or block + + --> src/fifth.rs:31:13 + | +31 | (*self.tail).next = Some(new_tail); + | ^^^^^^^^^^^^^^^^^ dereference of raw pointer + | + = note: raw pointers may be NULL, dangling or unaligned; + they can violate aliasing rules and cause data races: + all of these are undefined behavior +``` + +哎...太难了,错误一个连一个,好在编译器给出了提示:由于我们在进行不安全的操作,因此需要使用 `unsafe` 语句块。那么问题来了,是将某几行代码包在 `unsafe` 中还是将整个函数包在 `unsafe` 中呢?如果大家不知道哪个是正确答案的话,证明[之前的章节](https://course.rs/advance/unsafe/intro.html#控制-unsafe-的使用边界)还是没有仔细学,请回去再看一下,巩固巩固:) + +```rust +pub fn push(&mut self, elem: T) { + let mut new_tail = Box::new(Node { + elem: elem, + next: None, + }); + + let raw_tail: *mut _ = &mut *new_tail; + + if !self.tail.is_null() { + // 你好编译器,我知道我在做危险的事情,我向你保证:就算犯错了,也和你没有关系,都是我这个不优秀的程序员的责任 + unsafe { + (*self.tail).next = Some(new_tail); + } + } else { + self.head = Some(new_tail); + } + + self.tail = raw_tail; +} +``` + +```shell +> cargo build +warning: field is never used: `elem` + --> src/fifth.rs:11:5 + | +11 | elem: T, + | ^^^^^^^ + | + = note: #[warn(dead_code)] on by default +``` + +细心的同学可能会发现:不是所有的裸指针代码都有 unsafe 的身影。原因在于:**创建原生指针是安全的行为,而解引用原生指针才是不安全的行为** + +呼,长出了一口气,终于成功实现了 `push` ,下面来看看 `pop`: +```rust +pub fn pop(&mut self) -> Option { + self.head.take().map(|head| { + let head = *head; + self.head = head.next; + + if self.head.is_none() { + self.tail = ptr::null_mut(); + } + + head.elem + }) +} +``` + +测试下: +```rust +#[cfg(test)] +mod test { + use super::List; + #[test] + fn basics() { + let mut list = List::new(); + + // Check empty list behaves right + assert_eq!(list.pop(), None); + + // Populate list + list.push(1); + list.push(2); + list.push(3); + + // Check normal removal + assert_eq!(list.pop(), Some(1)); + assert_eq!(list.pop(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push(4); + list.push(5); + + // Check normal removal + assert_eq!(list.pop(), Some(3)); + assert_eq!(list.pop(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop(), Some(5)); + assert_eq!(list.pop(), None); + + // Check the exhaustion case fixed the pointer right + list.push(6); + list.push(7); + + // Check normal removal + assert_eq!(list.pop(), Some(6)); + assert_eq!(list.pop(), Some(7)); + assert_eq!(list.pop(), None); + } +} +``` + +摊牌了,我们偷懒了,这些测试就是从之前的栈链表赋值过来的,但是依然做了些改变,例如在末尾增加了几个步骤以确保在 `pop` 中不会发生尾指针损坏( tail-pointer corruption )的情况。 + +```shell +cargo test + +running 12 tests +test fifth::test::basics ... ok +test first::test::basics ... ok +test fourth::test::basics ... ok +test fourth::test::peek ... ok +test second::test::basics ... ok +test fourth::test::into_iter ... ok +test second::test::into_iter ... ok +test second::test::iter ... ok +test second::test::iter_mut ... ok +test second::test::peek ... ok +test third::test::basics ... ok +test third::test::iter ... ok + +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured +``` + diff --git a/src/too-many-lists/unsafe-queue/intro.md b/src/too-many-lists/unsafe-queue/intro.md new file mode 100644 index 00000000..71d84046 --- /dev/null +++ b/src/too-many-lists/unsafe-queue/intro.md @@ -0,0 +1,20 @@ +# 不错的unsafe队列 +在之前章节中,基于内部可变性和引用计数的双向链表有些失控了,原因在于 `Rc` 和 `RefCell` 对于简单的任务而言,它们是非常称职的,但是对于复杂的任务,它们可能会变得相当笨拙,特别是当我们试图隐藏一些细节时。 + +总之,一定有更好的办法!下面来看看该如何使用裸指针和 unsafe 代码实现一个单向链表。 + +> 大家可能想等着看我犯错误,unsafe 嘛,不犯错误不可能的,但是呢,俺偏就不犯错误:) + +国际惯例,添加第五个链表所需的文件 `fifth.rs`: +```rust +// in lib.rs + +pub mod first; +pub mod second; +pub mod third; +pub mod fourth; +pub mod fifth; +``` + +虽然我们依然会从零开始撸代码,但是 `fifth.rs` 的代码会跟 `second.rs` 存在一定的重叠,因为对于链表而言,队列其实就是栈的增强。 + diff --git a/src/too-many-lists/unsafe-queue/layout.md b/src/too-many-lists/unsafe-queue/layout.md new file mode 100644 index 00000000..6ed601e6 --- /dev/null +++ b/src/too-many-lists/unsafe-queue/layout.md @@ -0,0 +1,374 @@ +# 数据布局 +那么单向链表的队列长什么样?对于栈来说,我们向一端推入( push )元素,然后再从同一端弹出( pop )。对于栈和队列而言,唯一的区别在于队列从末端弹出。 + +栈的实现类似于下图: +```shell +input list: +[Some(ptr)] -> (A, Some(ptr)) -> (B, None) + +stack push X: +[Some(ptr)] -> (X, Some(ptr)) -> (A, Some(ptr)) -> (B, None) + +stack pop: +[Some(ptr)] -> (A, Some(ptr)) -> (B, None) +``` + +由于队列是首端进,末端出,因此我们需要决定将 `push` 和 `pop` 中的哪个放到末端去操作,如果将 `push` 放在末端操作: +```shell +input list: +[Some(ptr)] -> (A, Some(ptr)) -> (B, None) + +flipped push X: +[Some(ptr)] -> (A, Some(ptr)) -> (B, Some(ptr)) -> (X, None) +``` + +而如果将 `pop` 放在末端: +```shell +input list: +[Some(ptr)] -> (A, Some(ptr)) -> (B, Some(ptr)) -> (X, None) + +flipped pop: +[Some(ptr)] -> (A, Some(ptr)) -> (B, None) +``` + +但是这样实现有一个很糟糕的地方:两个操作都需要遍历整个链表后才能完成。队列要求 `push` 和 `pop` 操作需要高效,但是遍历整个链表才能完成的操作怎么看都谈不上高效! + +其中一个解决办法就是保存一个指针指向末端: +```rust +use std::mem; + +pub struct List { + head: Link, + tail: Link, // NEW! +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +impl List { + pub fn new() -> Self { + List { head: None, tail: None } + } + + pub fn push(&mut self, elem: T) { + let new_tail = Box::new(Node { + elem: elem, + // 在尾端推入一个新节点时,新节点的下一个节点永远是 None + next: None, + }); + + // 让 tail 指向新的节点,并返回之前的 old tail + let old_tail = mem::replace(&mut self.tail, Some(new_tail)); + + match old_tail { + Some(mut old_tail) => { + // 若 old tail 存在,则让该节点指向新的节点 + old_tail.next = Some(new_tail); + } + None => { + // 否则,将 head 指向新的节点 + self.head = Some(new_tail); + } + } + } +} +``` + +在之前的各种链表锤炼下,我们对于相关代码应该相当熟悉了,因此可以适当提提速 - 在写的过程中,事实上我碰到了很多错误,这些错误就不再一一列举。 + +但是如果你担心不再能看到错误,那就纯属多余了: +```shell +> cargo build + +error[E0382]: use of moved value: `new_tail` + --> src/fifth.rs:38:38 + | +26 | let new_tail = Box::new(Node { + | -------- move occurs because `new_tail` has type `std::boxed::Box>`, which does not implement the `Copy` trait +... +33 | let old_tail = mem::replace(&mut self.tail, Some(new_tail)); + | -------- value moved here +... +38 | old_tail.next = Some(new_tail); + | ^^^^^^^^ value used here after move +``` + +新鲜出炉的错误,接好!`Box` 并没有实现 `Copy` 特征,因此我们不能在两个地方进行赋值。好在,可以使用没有所有权的引用类型: +```rust +pub struct List { + head: Link, + tail: Option<&mut Node>, // NEW! +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +impl List { + pub fn new() -> Self { + List { head: None, tail: None } + } + + pub fn push(&mut self, elem: T) { + let new_tail = Box::new(Node { + elem: elem, + next: None, + }); + + let new_tail = match self.tail.take() { + Some(old_tail) => { + old_tail.next = Some(new_tail); + old_tail.next.as_deref_mut() + } + None => { + self.head = Some(new_tail); + self.head.as_deref_mut() + } + }; + + self.tail = new_tail; + } +} +``` + +```shell +> cargo build + +error[E0106]: missing lifetime specifier + --> src/fifth.rs:3:18 + | +3 | tail: Option<&mut Node>, // NEW! + | ^ expected lifetime parameter +``` + +好吧,结构体中的引用类型需要显式的标注生命周期,先加一个 `'a` 吧: +```rust +pub struct List<'a, T> { + head: Link, + tail: Option<&'a mut Node>, // NEW! +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} + +impl<'a, T> List<'a, T> { + pub fn new() -> Self { + List { head: None, tail: None } + } + + pub fn push(&mut self, elem: T) { + let new_tail = Box::new(Node { + elem: elem, + next: None, + }); + + let new_tail = match self.tail.take() { + Some(old_tail) => { + old_tail.next = Some(new_tail); + old_tail.next.as_deref_mut() + } + None => { + self.head = Some(new_tail); + self.head.as_deref_mut() + } + }; + + self.tail = new_tail; + } +} +``` + +```shell +cargo build + +error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements + --> src/fifth.rs:35:27 + | +35 | self.head.as_deref_mut() + | ^^^^^^^^^^^^ + | +note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 18:5... + --> src/fifth.rs:18:5 + | +18 | / pub fn push(&mut self, elem: T) { +19 | | let new_tail = Box::new(Node { +20 | | elem: elem, +21 | | // When you push onto the tail, your next is always None +... | +39 | | self.tail = new_tail; +40 | | } + | |_____^ +note: ...so that reference does not outlive borrowed content + --> src/fifth.rs:35:17 + | +35 | self.head.as_deref_mut() + | ^^^^^^^^^ +note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 13:6... + --> src/fifth.rs:13:6 + | +13 | impl<'a, T> List<'a, T> { + | ^^ + = note: ...so that the expression is assignable: + expected std::option::Option<&'a mut fifth::Node> + found std::option::Option<&mut fifth::Node> +``` + +好长... Rust 为啥这么难... 但是,这里有一句重点: + +> the lifetime must be valid for the lifetime 'a as defined on the impl + +意思是说生命周期至少要和 `'a` 一样长,是不是因为编译器为 `self` 推导的生命周期不够长呢?我们试着来手动标注下: +```rust +pub fn push(&'a mut self, elem: T) { +``` + +当当当当,成功通过编译: +```shell +cargo build + +warning: field is never used: `elem` + --> src/fifth.rs:9:5 + | +9 | elem: T, + | ^^^^^^^ + | + = note: #[warn(dead_code)] on by default +``` + +这个错误可以称之为错误之王,但是我们依然成功的解决了它,太棒了!再来实现下 `pop`: +```rust +pub fn pop(&'a mut self) -> Option { + self.head.take().map(|head| { + let head = *head; + self.head = head.next; + + if self.head.is_none() { + self.tail = None; + } + + head.elem + }) +} +``` + +看起来不错,写几个测试用例溜一溜: +```rust +mod test { + use super::List; + #[test] + fn basics() { + let mut list = List::new(); + + // Check empty list behaves right + assert_eq!(list.pop(), None); + + // Populate list + list.push(1); + list.push(2); + list.push(3); + + // Check normal removal + assert_eq!(list.pop(), Some(1)); + assert_eq!(list.pop(), Some(2)); + + // Push some more just to make sure nothing's corrupted + list.push(4); + list.push(5); + + // Check normal removal + assert_eq!(list.pop(), Some(3)); + assert_eq!(list.pop(), Some(4)); + + // Check exhaustion + assert_eq!(list.pop(), Some(5)); + assert_eq!(list.pop(), None); + } +} +``` +```shell +cargo test + +error[E0499]: cannot borrow `list` as mutable more than once at a time + --> src/fifth.rs:68:9 + | +65 | assert_eq!(list.pop(), None); + | ---- first mutable borrow occurs here +... +68 | list.push(1); + | ^^^^ + | | + | second mutable borrow occurs here + | first borrow later used here + +error[E0499]: cannot borrow `list` as mutable more than once at a time + --> src/fifth.rs:69:9 + | +65 | assert_eq!(list.pop(), None); + | ---- first mutable borrow occurs here +... +69 | list.push(2); + | ^^^^ + | | + | second mutable borrow occurs here + | first borrow later used here + +error[E0499]: cannot borrow `list` as mutable more than once at a time + --> src/fifth.rs:70:9 + | +65 | assert_eq!(list.pop(), None); + | ---- first mutable borrow occurs here +... +70 | list.push(3); + | ^^^^ + | | + | second mutable borrow occurs here + | first borrow later used here + + +.... + +** WAY MORE LINES OF ERRORS ** + +.... + +error: aborting due to 11 previous errors +``` + +🙀🙀🙀,震惊!但编译器真的没错,因为都是我们刚才那个标记惹的祸。 + +我们为 `self` 标记了 `'a`,意味着在 `'a` 结束前,无法再去使用 `self`,大家可以自己推断下 `'a` 的生命周期是多长。 + +那么该怎么办?回到老路 `RefCell` 上?显然不可能,那只能祭出大杀器:裸指针。 + +> 事实上,上文的问题主要是自引用引起的,感兴趣的同学可以查看[这里](https://course.rs/advance/circle-self-ref/intro.html)深入阅读。 + +```rust +pub struct List { + head: Link, + tail: *mut Node, // DANGER DANGER +} + +type Link = Option>>; + +struct Node { + elem: T, + next: Link, +} +``` + +如上所示,当使用裸指针后, `head` 和 `tail` 就不会形成自引用的问题,也不再违反 Rust 严苛的借用规则。 + +> 注意!当前的实现依然是有严重问题的,在后面我们会修复 + +果然,编程的最高境界就是回归本质:使用 C 语言的东东。 \ No newline at end of file diff --git a/src/too-many-lists/unsafe-queue/miri.md b/src/too-many-lists/unsafe-queue/miri.md new file mode 100644 index 00000000..0cb65bf5 --- /dev/null +++ b/src/too-many-lists/unsafe-queue/miri.md @@ -0,0 +1,175 @@ +# Miri +看到这里,大家是不是暗中松了口气?unsafe 不过如此嘛,不知道为何其它人都谈之色变。 + +怎么说呢?你以为的编译器已经不是以前的编译器了,它不报错不代表没有错误。包括测试用例也是,正常地运行不能意味着代码没有任何错误。 + +在周星驰电影功夫中,还有一个奇怪大叔 10 元一本主动上门卖如来神掌,那么有没有 10 元一本的 Rust 秘笈呢?( 喂,Rust语言圣经都免费让你读了,有了摩托车,还要什么拖拉机... 哈哈,开个玩笑 ) + +有的,奇怪大叔正在赶来,他告诉我们先来安装一个命令: +```shell +rustup +nightly-2022-01-21 component add miri +info: syncing channel updates for 'nightly-2022-01-21-x86_64-pc-windows-msvc' +info: latest update on 2022-01-21, rust version 1.60.0-nightly (777bb86bc 2022-01-20) +info: downloading component 'cargo' +info: downloading component 'clippy' +info: downloading component 'rust-docs' +info: downloading component 'rust-std' +info: downloading component 'rustc' +info: downloading component 'rustfmt' +info: installing component 'cargo' +info: installing component 'clippy' +info: installing component 'rust-docs' +info: installing component 'rust-std' +info: installing component 'rustc' +info: installing component 'rustfmt' +info: downloading component 'miri' +info: installing component 'miri' +``` + +等等,你在我电脑上装了什么奇怪的东西?! "好东西" + + +> miri 目前只能在 nightly Rust 上安装,`+nightly-2022-01-21` 告诉 `rustup` 我们想要安装的 `nightly` 版本,事实上,你可以直接通过 `rustup +nightly component add miri` 安装,这里指定版本主要因为 `miri` 有时候会因为某些版本而出错。 +> +> 2022-01-21 是我所知的 miri 可以成功运行的版本,你可以检查[这个网址](https://rust-lang.github.io/rustup-components-history/)获取更多信息 +> +> + 是一种临时性的规则运用,如果你不想每次都使用 `+nightly-2022-01-21`,可以使用 [`rustup override set`](https://course.rs/appendix/rust-version.html#rustup-和-rust-nightly-的职责) 命令对当前项目的 Rust 版本进行覆盖 + +```shell +> cargo +nightly-2022-01-21 miri test + +I will run `"cargo.exe" "install" "xargo"` to install +a recent enough xargo. Proceed? [Y/n] +``` + +额,`xargo` 是什么东东?"不要担心,选择 y 就行,我像是会坑你的人吗?" + +```shell +> y + + Updating crates.io index + Installing xargo v0.3.24 +... + Finished release [optimized] target(s) in 10.65s + Installing C:\Users\ninte\.cargo\bin\xargo-check.exe + Installing C:\Users\ninte\.cargo\bin\xargo.exe + Installed package `xargo v0.3.24` (executables `xargo-check.exe`, `xargo.exe`) + +I will run `"rustup" "component" "add" "rust-src"` to install +the `rust-src` component for the selected toolchain. Proceed? [Y/n] +``` + +额? "不要怕,多给你一份 Rust 源代码,不开心嘛?" + +```shell +> y + +info: downloading component 'rust-src' +info: installing component 'rust-src' +``` + +"看吧,我就说我不会骗你的,不相信我,等着错误砸脸吧!" 真是一个奇怪的大叔... +```shell + Compiling lists v0.1.0 (C:\Users\ninte\dev\tmp\lists) + Finished test [unoptimized + debuginfo] target(s) in 0.25s + Running unittests (lists-5cc11d9ee5c3e924.exe) + +error: Undefined Behavior: trying to reborrow for Unique at alloc84055, + but parent tag <209678> does not have an appropriate item in + the borrow stack + + --> \lib\rustlib\src\rust\library\core\src\option.rs:846:18 + | +846 | Some(x) => Some(f(x)), + | ^ trying to reborrow for Unique at alloc84055, + | but parent tag <209678> does not have an + | appropriate item in the borrow stack + | + = help: this indicates a potential bug in the program: + it performed an invalid operation, but the rules it + violated are still experimental + = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md + for further information + + = note: inside `std::option::Option::>>::map::` at \lib\rustlib\src\rust\library\core\src\option.rs:846:18 + +note: inside `fifth::List::::pop` at src\fifth.rs:31:9 + --> src\fifth.rs:31:9 + | +31 | / self.head.take().map(|head| { +32 | | let head = *head; +33 | | self.head = head.next; +34 | | +... | +39 | | head.elem +40 | | }) + | |__________^ +note: inside `fifth::test::basics` at src\fifth.rs:74:20 + --> src\fifth.rs:74:20 + | +74 | assert_eq!(list.pop(), Some(1)); + | ^^^^^^^^^^ +note: inside closure at src\fifth.rs:62:5 + --> src\fifth.rs:62:5 + | +61 | #[test] + | ------- in this procedural macro expansion +62 | / fn basics() { +63 | | let mut list = List::new(); +64 | | +65 | | // Check empty list behaves right +... | +96 | | assert_eq!(list.pop(), None); +97 | | } + | |_____^ + ... +error: aborting due to previous error +``` + +咦还真有错误,大叔,这是什么错误?大叔?...奇怪的大叔默默离开了,留下我在风中凌乱。 + +果然不靠谱...还是得靠自己,首先得了解下何为 `miri`。 + + +[`miri`](https://github.com/rust-lang/miri) 可以生成 Rust 的中间层表示 MIR,对于编译器来说,我们的 Rust 代码首先会被编译为 MIR ,然后再提交给 LLVM 进行处理。 + +可以通过 `rustup component add miri` 来安装它,并通过 `cargo miri` 来使用,同时还可以使用 `cargo miri test` 来运行测试代码。 + +`miri` 可以帮助我们检查常见的未定义行为(UB = Undefined Behavior),以下列出了一部分: + +- 内存越界检查和内存释放后再使用(use-after-free) +- 使用未初始化的数据 +- 数据竞争 +- 内存对齐问题 + +UB 检测是必须的,因为它发生在运行时,因此很难发现,如果 `miri` 能在编译期检测出来,那自然是最好不过的。 + +总之,`miri` 的使用很简单: +```shell +> cargo +nightly-2022-01-21 miri test +``` + +下面来看看具体的错误: +```shell +error: Undefined Behavior: trying to reborrow for Unique at alloc84055, but parent tag <209678> does not have an appropriate item in the borrow stack + + --> \lib\rustlib\src\rust\library\core\src\option.rs:846:18 + | +846 | Some(x) => Some(f(x)), + | ^ trying to reborrow for Unique at alloc84055, + | but parent tag <209678> does not have an + | appropriate item in the borrow stack + | + + = help: this indicates a potential bug in the program: it + performed an invalid operation, but the rules it + violated are still experimental + + = help: see + https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md + for further information +``` + +嗯,只能看出是一个错误,其它完全看不懂了,例如什么是 `borrow stack`? + + diff --git a/src/cargo/getting-started.md b/src/toolchains/cargo/getting-started.md similarity index 100% rename from src/cargo/getting-started.md rename to src/toolchains/cargo/getting-started.md diff --git a/src/cargo/git-auth.md b/src/toolchains/cargo/git-auth.md similarity index 100% rename from src/cargo/git-auth.md rename to src/toolchains/cargo/git-auth.md diff --git a/src/cargo/guide/build-cache.md b/src/toolchains/cargo/guide/build-cache.md similarity index 80% rename from src/cargo/guide/build-cache.md rename to src/toolchains/cargo/guide/build-cache.md index 2eddbc83..7b5a4d63 100644 --- a/src/cargo/guide/build-cache.md +++ b/src/toolchains/cargo/guide/build-cache.md @@ -1,6 +1,6 @@ # 构建( Build )缓存 -`cargo build` 的结果会被放入项目根目录下的 `target` 文件夹中,当然,这个位置可以三种方式更改:设置 `CARGO_TARGET_DIR` [环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html)、[`build.target-dir`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 配置项以及 `--target-dir` 命令行参数。 +`cargo build` 的结果会被放入项目根目录下的 `target` 文件夹中,当然,这个位置可以三种方式更改:设置 `CARGO_TARGET_DIR` [环境变量](https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html)、[`build.target-dir`](https://course.rs/toolchains/cargo/reference/configuration.html#配置文件概览) 配置项以及 `--target-dir` 命令行参数。 ## target 目录结构 @@ -8,7 +8,7 @@ #### 不使用 --target -若 `--target` 标志没有指定,`Cargo` 会根据宿主机架构进行构建,构建结果会放入项目根目录下的 `target` 目录中,`target` 下每个子目录中包含了相应的 [`发布配置profile`](https://course.rs/cargo/reference/profiles.html) 的构建结果,例如 `release`、`debug` 是自带的`profile`,前者往往用于生产环境,因为会做大量的性能优化,而后者则用于开发环境,此时的编译效率和报错信息是最好的。 +若 `--target` 标志没有指定,`Cargo` 会根据宿主机架构进行构建,构建结果会放入项目根目录下的 `target` 目录中,`target` 下每个子目录中包含了相应的 [`发布配置profile`](https://course.rs/toolchains/cargo/reference/profiles.html) 的构建结果,例如 `release`、`debug` 是自带的`profile`,前者往往用于生产环境,因为会做大量的性能优化,而后者则用于开发环境,此时的编译效率和报错信息是最好的。 除此之外我们还可以定义自己想要的 `profile` ,例如用于测试环境的 `profile`: `test`,用于预发环境的 `profile` :`pre-prod` 等。 @@ -33,7 +33,7 @@ | `target//debug/` | `target/thumbv7em-none-eabihf/debug/` | | `target//release/` | `target/thumbv7em-none-eabihf/release/` | -> **注意:**,当没有使用 `--target` 时,`Cargo` 会与构建脚本和过程宏一起共享你的依赖包,对于每个 `rustc` 命令调用而言,[`RUSTFLAGS`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 也将被共享。 +> **注意:**,当没有使用 `--target` 时,`Cargo` 会与构建脚本和过程宏一起共享你的依赖包,对于每个 `rustc` 命令调用而言,[`RUSTFLAGS`](https://course.rs/toolchains/cargo/reference/configuration.html#配置文件概览) 也将被共享。 > > 而使用 `--target` 后,构建脚本、过程宏会针对宿主机的 CPU 架构进行各自构建,且不会共享 `RUSTFLAGS`。 @@ -43,8 +43,8 @@ | 目录 | 描述 | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | -| `target/debug/` | 包含编译后的输出,例如二进制可执行文件、[库对象( library target )](https://course.rs/cargo/reference/cargo-target.html#库对象library) | -| `target/debug/examples/` | 包含[示例对象( example target )](https://course.rs/cargo/reference/cargo-target.html#示例对象examples) | +| `target/debug/` | 包含编译后的输出,例如二进制可执行文件、[库对象( library target )](https://course.rs/toolchains/cargo/reference/cargo-target.html#库对象library) | +| `target/debug/examples/` | 包含[示例对象( example target )](https://course.rs/toolchains/cargo/reference/cargo-target.html#示例对象examples) | 还有一些命令会在 `target` 下生成自己的独立目录: @@ -58,8 +58,8 @@ Cargo 还会创建几个用于构建过程的其它类型目录,它们的目 | 目录 | 描述 | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `target/debug/deps` | 依赖和其它输出成果 | -| `target/debug/incremental` | `rustc` [增量编译](https://course.rs/cargo/reference/profiles.html#incremental)的输出,该缓存可以用于提升后续的编译速度 | -| `target/debug/build/` | [构建脚本](https://course.rs/cargo/reference/build-script/intro.html)的输出 | +| `target/debug/incremental` | `rustc` [增量编译](https://course.rs/toolchains/cargo/reference/profiles.html#incremental)的输出,该缓存可以用于提升后续的编译速度 | +| `target/debug/build/` | [构建脚本](https://course.rs/toolchains/cargo/reference/build-script/intro.html)的输出 | ## 依赖信息文件 @@ -67,7 +67,7 @@ Cargo 还会创建几个用于构建过程的其它类型目录,它们的目 该文件往往用于提供给外部的构建系统,这样它们就可以判断 `Cargo` 命令是否需要再次被执行。 -文件中的路径默认是绝对路径,你可以通过 [`build.dep-info-basedir`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 配置项来修改为相对路径。 +文件中的路径默认是绝对路径,你可以通过 [`build.dep-info-basedir`](https://course.rs/toolchains/cargo/reference/configuration.html#配置文件概览) 配置项来修改为相对路径。 ```shell # 关于 `.d` 文件的一个示例 : target/debug/foo.d @@ -81,4 +81,4 @@ Cargo 还会创建几个用于构建过程的其它类型目录,它们的目 为了设置 `sccache`,首先需要使用 `cargo install sccache` 进行安装,然后在调用 `Cargo` 之前将 `RUSTC_WRAPPER` 环境变量设置为 `sccache`。 - 如果用的 `bash`,可以将 `export RUSTC_WRAPPER=sccache` 添加到 `.bashrc` 中 -- 也可以使用 [`build.rustc-wrapper`](https://course.rs/cargo/reference/configuration.html#配置文件概览) 配置项 +- 也可以使用 [`build.rustc-wrapper`](https://course.rs/toolchains/cargo/reference/configuration.html#配置文件概览) 配置项 diff --git a/src/cargo/guide/cargo-cache.md b/src/toolchains/cargo/guide/cargo-cache.md similarity index 95% rename from src/cargo/guide/cargo-cache.md rename to src/toolchains/cargo/guide/cargo-cache.md index 831a61fc..5bec0313 100644 --- a/src/cargo/guide/cargo-cache.md +++ b/src/toolchains/cargo/guide/cargo-cache.md @@ -17,7 +17,7 @@ $ echo $HOME/.cargo/ ## 文件 -- `config.toml` 是 Cargo 的全局配置文件,具体请查看[这里](https://course.rs/cargo/reference/configuration.html) +- `config.toml` 是 Cargo 的全局配置文件,具体请查看[这里](https://course.rs/toolchains/cargo/reference/configuration.html) - `credentials.toml` 为 `cargo login` 提供私有化登录证书,用于登录 `package` 注册中心,例如 `crates.io` - `.crates.toml`, `.crates2.json` 这两个是隐藏文件,包含了通过 `cargo install` 安装的包的 `package` 信息,**请不要手动修改!** @@ -65,7 +65,7 @@ $ echo $HOME/.cargo/ 解决办法很简单: -- 既然下载慢,那就使用[国内的注册服务](https://course.rs/cargo/reference/specify-deps.html#从其它注册服务引入依赖包),不再使用 crates.io +- 既然下载慢,那就使用[国内的注册服务](https://course.rs/toolchains/cargo/reference/specify-deps.html#从其它注册服务引入依赖包),不再使用 crates.io - 耐心等待持有锁的用户构建完成 - 强行停止正在构建的进程,例如杀掉 IDE 使用的 rust-analyer 插件进程,然后删除 `$HOME/.cargo/.package_cache` 目录 diff --git a/src/cargo/guide/cargo-toml-lock.md b/src/toolchains/cargo/guide/cargo-toml-lock.md similarity index 100% rename from src/cargo/guide/cargo-toml-lock.md rename to src/toolchains/cargo/guide/cargo-toml-lock.md diff --git a/src/cargo/guide/dependencies.md b/src/toolchains/cargo/guide/dependencies.md similarity index 95% rename from src/cargo/guide/dependencies.md rename to src/toolchains/cargo/guide/dependencies.md index 851296b5..5ae02ddb 100644 --- a/src/cargo/guide/dependencies.md +++ b/src/toolchains/cargo/guide/dependencies.md @@ -9,7 +9,7 @@ time = "0.1.12" ``` -可以看到我们指定了 `time` 包的版本号 "0.1.12",关于版本号,实际上还有其它的指定方式,具体参见[指定依赖项](https://course.rs/cargo/reference/specify-deps.html)章节。 +可以看到我们指定了 `time` 包的版本号 "0.1.12",关于版本号,实际上还有其它的指定方式,具体参见[指定依赖项](https://course.rs/toolchains/cargo/reference/specify-deps.html)章节。 如果想继续添加 `regexp` 包,只需在 `time` 包后面添加即可 : diff --git a/src/cargo/guide/download-package.md b/src/toolchains/cargo/guide/download-package.md similarity index 100% rename from src/cargo/guide/download-package.md rename to src/toolchains/cargo/guide/download-package.md diff --git a/src/cargo/guide/intro.md b/src/toolchains/cargo/guide/intro.md similarity index 100% rename from src/cargo/guide/intro.md rename to src/toolchains/cargo/guide/intro.md diff --git a/src/cargo/guide/package-layout.md b/src/toolchains/cargo/guide/package-layout.md similarity index 95% rename from src/cargo/guide/package-layout.md rename to src/toolchains/cargo/guide/package-layout.md index a0b9f336..653adcc3 100644 --- a/src/cargo/guide/package-layout.md +++ b/src/toolchains/cargo/guide/package-layout.md @@ -45,5 +45,5 @@ 关于 Rust 中的包和模块,[之前的章节](https://course.rs/basic/crate-module/intro.html)有更详细的解释。 -此外,`bin`、`tests`、`examples` 等目录路径都可以通过配置文件进行配置,它们被统一称之为 [Cargo Target](https://course.rs/cargo/reference/cargo-target.html)。 +此外,`bin`、`tests`、`examples` 等目录路径都可以通过配置文件进行配置,它们被统一称之为 [Cargo Target](https://course.rs/toolchains/cargo/reference/cargo-target.html)。 diff --git a/src/cargo/guide/tests-ci.md b/src/toolchains/cargo/guide/tests-ci.md similarity index 100% rename from src/cargo/guide/tests-ci.md rename to src/toolchains/cargo/guide/tests-ci.md diff --git a/src/cargo/guide/why-exist.md b/src/toolchains/cargo/guide/why-exist.md similarity index 100% rename from src/cargo/guide/why-exist.md rename to src/toolchains/cargo/guide/why-exist.md diff --git a/src/cargo/intro.md b/src/toolchains/cargo/intro.md similarity index 100% rename from src/cargo/intro.md rename to src/toolchains/cargo/intro.md diff --git a/src/cargo/reference/build-script/examples.md b/src/toolchains/cargo/reference/build-script/examples.md similarity index 97% rename from src/cargo/reference/build-script/examples.md rename to src/toolchains/cargo/reference/build-script/examples.md index cdba5d5f..9a7694de 100644 --- a/src/cargo/reference/build-script/examples.md +++ b/src/toolchains/cargo/reference/build-script/examples.md @@ -250,7 +250,7 @@ fn test_crc32() { } ``` -代码很清晰,也很简洁,这里就不再过多介绍,运行 [`cargo build --vv`](https://course.rs/cargo/reference/build-script/intro.html#构建脚本的输出) 来看看部分结果( 系统中需要已经安装 `libz` 库): +代码很清晰,也很简洁,这里就不再过多介绍,运行 [`cargo build --vv`](https://course.rs/toolchains/cargo/reference/build-script/intro.html#构建脚本的输出) 来看看部分结果( 系统中需要已经安装 `libz` 库): ```shell [libz-sys 0.1.0] cargo:rustc-link-search=native=/usr/lib @@ -268,7 +268,7 @@ fn test_crc32() { 若你有一个依赖于 `zlib` 的库,那可以使用 `libz-sys` 来自动发现或构建该库。这个功能对于交叉编译非常有用,例如 Windows 下往往不会安装 `zlib`。 -`libz-sys` 通过设置 [`include`](https://github.com/rust-lang/libz-sys/blob/3c594e677c79584500da673f918c4d2101ac97a1/build.rs#L156) 元数据来告知其它包去哪里找到 `zlib` 的头文件,然后我们的构建脚本可以通过 `DEP_Z_INCLUDE` 环境变量来读取 `include` 元数据( 关于元数据的传递,见[这里](https://course.rs/cargo/reference/build-script/intro.html#links) )。 +`libz-sys` 通过设置 [`include`](https://github.com/rust-lang/libz-sys/blob/3c594e677c79584500da673f918c4d2101ac97a1/build.rs#L156) 元数据来告知其它包去哪里找到 `zlib` 的头文件,然后我们的构建脚本可以通过 `DEP_Z_INCLUDE` 环境变量来读取 `include` 元数据( 关于元数据的传递,见[这里](https://course.rs/toolchains/cargo/reference/build-script/intro.html#links) )。 ```toml # Cargo.toml diff --git a/src/cargo/reference/build-script/intro.md b/src/toolchains/cargo/reference/build-script/intro.md similarity index 95% rename from src/cargo/reference/build-script/intro.md rename to src/toolchains/cargo/reference/build-script/intro.md index edd2842a..e2653d0f 100644 --- a/src/cargo/reference/build-script/intro.md +++ b/src/toolchains/cargo/reference/build-script/intro.md @@ -28,9 +28,9 @@ fn main() { - 根据某个说明描述文件生成一个 Rust 模块 - 执行一些平台相关的配置 -下面的部分我们一起来看看构建脚本具体是如何工作的,然后在[下个章节](https://course.rs/cargo/reference/build-script/examples.html)中还提供了一些关于如何编写构建脚本的示例。 +下面的部分我们一起来看看构建脚本具体是如何工作的,然后在[下个章节](https://course.rs/toolchains/cargo/reference/build-script/examples.html)中还提供了一些关于如何编写构建脚本的示例。 -> Note: [`package.build`](https://course.rs/cargo/reference/manifest.html#build) 可以用于改变构建脚本的名称,或者直接禁用该功能 +> Note: [`package.build`](https://course.rs/toolchains/cargo/reference/manifest.html#build) 可以用于改变构建脚本的名称,或者直接禁用该功能 #### 构建脚本的生命期 @@ -121,7 +121,7 @@ Cargo 要求一个本地库最多只能被一个项目所链接,换而言之 ## 覆盖构建脚本 -当 `Cargo.toml` 设置了 `links` 时, Cargo 就允许我们使用自定义库对现有的构建脚本进行覆盖。在 [Cargo 使用的配置文件](https://course.rs/cargo/reference/configuration.html)中添加以下内容: +当 `Cargo.toml` 设置了 `links` 时, Cargo 就允许我们使用自定义库对现有的构建脚本进行覆盖。在 [Cargo 使用的配置文件](https://course.rs/toolchains/cargo/reference/configuration.html)中添加以下内容: ```toml [target.x86_64-unknown-linux-gnu.foo] diff --git a/src/cargo/reference/cargo-target.md b/src/toolchains/cargo/reference/cargo-target.md similarity index 93% rename from src/cargo/reference/cargo-target.md rename to src/toolchains/cargo/reference/cargo-target.md index 609ca316..952a73a1 100644 --- a/src/cargo/reference/cargo-target.md +++ b/src/toolchains/cargo/reference/cargo-target.md @@ -1,6 +1,6 @@ # Cargo Target -**Cargo 项目中包含有一些对象,它们包含的源代码文件可以被编译成相应的包,这些对象被称之为 Cargo Target**。例如[之前章节](https://course.rs/cargo/guide/package-layout.html)提到的库对象 `Library` 、二进制对象 `Binary`、示例对象 `Examples`、测试对象 `Tests` 和 基准性能对象 `Benches` 都是 Cargo Target。 +**Cargo 项目中包含有一些对象,它们包含的源代码文件可以被编译成相应的包,这些对象被称之为 Cargo Target**。例如[之前章节](https://course.rs/toolchains/cargo/guide/package-layout.html)提到的库对象 `Library` 、二进制对象 `Binary`、示例对象 `Examples`、测试对象 `Tests` 和 基准性能对象 `Benches` 都是 Cargo Target。 本章节我们一起来看看该如何在 `Cargo.toml` 清单中配置这些对象,当然,大部分时候都无需手动配置,因为默认的配置通常由项目目录的布局自动推断出来。 @@ -137,7 +137,7 @@ required-features = [] # 构建对象所需的 Cargo Features (N/A for lib). #### required-features -该字段用于指定在构建对象时所需的 [`features`](https://course.rs/cargo/reference/features.html) 列表。 +该字段用于指定在构建对象时所需的 [`features`](https://course.rs/toolchains/cargo/reference/features.html) 列表。 该字段只对 `[[bin]]`、 `[[bench]]`、 `[[test]]` 和 `[[example]]` 有效,对于 `[lib]` 没有任何效果。 @@ -155,7 +155,7 @@ required-features = ["postgres", "tools"] ## 对象自动发现 -默认情况下,`Cargo` 会基于项目的[目录文件布局](https://course.rs/cargo/guide/package-layout.html)自动发现和确定对象,而之前的配置项则允许我们对其进行手动的配置修改(若项目布局跟标准的不一样时)。 +默认情况下,`Cargo` 会基于项目的[目录文件布局](https://course.rs/toolchains/cargo/guide/package-layout.html)自动发现和确定对象,而之前的配置项则允许我们对其进行手动的配置修改(若项目布局跟标准的不一样时)。 而这种自动发现对象的设定可以通过以下配置来禁用: diff --git a/src/cargo/reference/configuration.md b/src/toolchains/cargo/reference/configuration.md similarity index 95% rename from src/cargo/reference/configuration.md rename to src/toolchains/cargo/reference/configuration.md index 8b531482..c29febe4 100644 --- a/src/cargo/reference/configuration.md +++ b/src/toolchains/cargo/reference/configuration.md @@ -1,6 +1,6 @@ # 通过 config.toml 对 Cargo 进行配置 -Cargo 相关的配置有两种,第一种是对自身进行配置,第二种是对指定的项目进行配置,关于后者请查看 [Cargo.toml 清单](https://course.rs/cargo/reference/manifest.html)。对于普通用户而言第二种才是我们最常使用的。 +Cargo 相关的配置有两种,第一种是对自身进行配置,第二种是对指定的项目进行配置,关于后者请查看 [Cargo.toml 清单](https://course.rs/toolchains/cargo/reference/manifest.html)。对于普通用户而言第二种才是我们最常使用的。 本文讲述的是如何对 Cargo 相关的工具进行配置,该配置中的部分内容可能会覆盖掉 `Cargo.toml` 中对应的部分,例如关于 `profile` 的内容。 @@ -95,7 +95,7 @@ offline = true # 不能访问网络 [patch.] # Same keys as for [patch] in Cargo.toml -[profile.] # profile 配置,详情见"如何在 Cargo.toml 中配置 profile" : https://course.rs/cargo/reference/profiles.html#profile设置 +[profile.] # profile 配置,详情见"如何在 Cargo.toml 中配置 profile" : https://course.rs/toolchains/cargo/reference/profiles.html#profile设置 opt-level = 0 debug = true split-debuginfo = '...' @@ -109,7 +109,7 @@ rpath = false [profile..build-override] [profile..package.] -[registries.] # 设置其它的注册服务: https://course.rs/cargo/reference/specify-deps.html#从其它注册服务引入依赖包 +[registries.] # 设置其它的注册服务: https://course.rs/toolchains/cargo/reference/specify-deps.html#从其它注册服务引入依赖包 index = "…" # 注册服务索引列表的 URL token = "…" # 连接注册服务所需的鉴权 token diff --git a/src/cargo/reference/deps-overriding.md b/src/toolchains/cargo/reference/deps-overriding.md similarity index 93% rename from src/cargo/reference/deps-overriding.md rename to src/toolchains/cargo/reference/deps-overriding.md index 74466d7c..9129578c 100644 --- a/src/cargo/reference/deps-overriding.md +++ b/src/toolchains/cargo/reference/deps-overriding.md @@ -9,7 +9,7 @@ 下面我们来具体看看类似的问题该如何解决。 -> 上一章节中我们讲了如果通过[多种引用方式](https://course.rs/cargo/reference/specify-deps/intro.html#多引用方式混合)来引入一个包,其实这也是一种依赖覆盖。 +> 上一章节中我们讲了如果通过[多种引用方式](https://course.rs/toolchains/cargo/reference/specify-deps/intro.html#多引用方式混合)来引入一个包,其实这也是一种依赖覆盖。 ## 测试 bugfix 版本 @@ -53,7 +53,7 @@ what is locked in the Cargo.lock file, run `cargo update` to use the new version. This may also occur with an optional dependency that is not enabled. ``` -具体原因比较复杂,但是仔细观察,会发现克隆下来的 `uuid` 的版本是 `v1.0.0-alpha.1` (在 `"../uuid/Cargo.toml"` 中可以查看),然后我们本地引入的 `uuid` 版本是 `0.8.2`,根据之前讲过的 `crates.io` 的[版本规则](https://course.rs/cargo/reference/specify-deps/intro.html#从-cratesio-引入依赖包),这两者是不兼容的,`0.8.2` 只能升级到 `0.8.z`,例如 `0.8.3`。 +具体原因比较复杂,但是仔细观察,会发现克隆下来的 `uuid` 的版本是 `v1.0.0-alpha.1` (在 `"../uuid/Cargo.toml"` 中可以查看),然后我们本地引入的 `uuid` 版本是 `0.8.2`,根据之前讲过的 `crates.io` 的[版本规则](https://course.rs/toolchains/cargo/reference/specify-deps/intro.html#从-cratesio-引入依赖包),这两者是不兼容的,`0.8.2` 只能升级到 `0.8.z`,例如 `0.8.3`。 既然如此,我们先将 "../uuid/Cargo.toml" 中的 `version = "1.0.0-alpha.1"` 修改为 `version = "0.8.3"` ,然后看看结果先: @@ -173,7 +173,7 @@ uuid = { git = 'https://github.com/uuid-rs/uuid', branch = '2.0.0' } ## 多版本[patch] -在之前章节,我们介绍过如何使用 `package key` 来[重命名依赖包](https://course.rs/cargo/reference/specify-deps/intro.html#在-cargotoml-中重命名依赖),现在来看看如何使用它同时引入多个 `patch`。 +在之前章节,我们介绍过如何使用 `package key` 来[重命名依赖包](https://course.rs/toolchains/cargo/reference/specify-deps/intro.html#在-cargotoml-中重命名依赖),现在来看看如何使用它同时引入多个 `patch`。 假设,我们对 `serde` 有两个新的 `patch` 需求: @@ -196,7 +196,7 @@ serde2 = { git = 'https://github.com/example/serde', package = 'serde', branch = 有时我们只是临时性地对一个项目进行处理,因此并不想去修改它的 `Cargo.toml`。此时可以使用 `Cargo` 提供的路径覆盖方法: **注意,这个方法限制较多,如果可以,还是要使用 [patch]**。 -与 `[patch]` 修改 `Cargo.toml` 不同,路径覆盖修改的是 `Cargo` 自身的[配置文件](https://course.rs/cargo/guide/cargo-cache.html#cargo-home) `$Home/.cargo/config.toml`: +与 `[patch]` 修改 `Cargo.toml` 不同,路径覆盖修改的是 `Cargo` 自身的[配置文件](https://course.rs/toolchains/cargo/guide/cargo-cache.html#cargo-home) `$Home/.cargo/config.toml`: ```toml paths = ["/path/to/uuid"] diff --git a/src/cargo/reference/env.md b/src/toolchains/cargo/reference/env.md similarity index 100% rename from src/cargo/reference/env.md rename to src/toolchains/cargo/reference/env.md diff --git a/src/cargo/reference/features/examples.md b/src/toolchains/cargo/reference/features/examples.md similarity index 100% rename from src/cargo/reference/features/examples.md rename to src/toolchains/cargo/reference/features/examples.md diff --git a/src/cargo/reference/features/intro.md b/src/toolchains/cargo/reference/features/intro.md similarity index 96% rename from src/cargo/reference/features/intro.md rename to src/toolchains/cargo/reference/features/intro.md index e91fa58d..03c03281 100644 --- a/src/cargo/reference/features/intro.md +++ b/src/toolchains/cargo/reference/features/intro.md @@ -87,7 +87,7 @@ avif = ["ravif", "rgb"] 之后,`avif` feature 一旦被启用,那这两个依赖库也将自动被引入。 -> 注意:我们之前也讲过条件引入依赖的方法,那就是使用[平台相关的依赖](https://course.rs/cargo/reference/specify-deps.html#根据平台引入依赖),与基于 feature 的可选依赖不同,它们是基于特定平台的可选依赖 +> 注意:我们之前也讲过条件引入依赖的方法,那就是使用[平台相关的依赖](https://course.rs/toolchains/cargo/reference/specify-deps.html#根据平台引入依赖),与基于 feature 的可选依赖不同,它们是基于特定平台的可选依赖 ## 依赖库自身的 feature @@ -249,11 +249,11 @@ V2 版本的解析器可以在某些情况下避免 feature 同一化的发生 ## 构建脚本 -[构建脚本](https://course.rs/cargo/reference/build-script/intro.html)可以通过 `CARGO_FEATURE_` 环境变量获取启用的 `feauture` 列表,其中 `` 是 feature 的名称,该名称被转换成大全写字母,且 `-` 被转换为 `_`。 +[构建脚本](https://course.rs/toolchains/cargo/reference/build-script/intro.html)可以通过 `CARGO_FEATURE_` 环境变量获取启用的 `feauture` 列表,其中 `` 是 feature 的名称,该名称被转换成大全写字母,且 `-` 被转换为 `_`。 ## required-features -该字段可以用于禁用特定的 Cargo Target:当某个 feature 没有被启用时,查看[这里](https://course.rs/cargo/reference/cargo-target.html#required-features)获取更多信息。 +该字段可以用于禁用特定的 Cargo Target:当某个 feature 没有被启用时,查看[这里](https://course.rs/toolchains/cargo/reference/cargo-target.html#required-features)获取更多信息。 ## SemVer 兼容性 diff --git a/src/cargo/reference/intro.md b/src/toolchains/cargo/reference/intro.md similarity index 100% rename from src/cargo/reference/intro.md rename to src/toolchains/cargo/reference/intro.md diff --git a/src/cargo/reference/manifest.md b/src/toolchains/cargo/reference/manifest.md similarity index 96% rename from src/cargo/reference/manifest.md rename to src/toolchains/cargo/reference/manifest.md index 5dccea4b..ea413c1d 100644 --- a/src/cargo/reference/manifest.md +++ b/src/toolchains/cargo/reference/manifest.md @@ -261,11 +261,11 @@ workspace = "path/to/workspace/root" - 该包是工作空间的根包(root crate),通过 `[workspace]` 指定) - 该包是另一个工作空间的成员,通过 `package.workspace` 指定 -若要了解工作空间的更多信息,请参见[这里](https://course.rs/cargo/reference/workspaces.html)。 +若要了解工作空间的更多信息,请参见[这里](https://course.rs/toolchains/cargo/reference/workspaces.html)。 #### build -`build` 用于指定位于项目根目录中的构建脚本,关于构建脚本的更多信息,可以阅读 [构建脚本](https://course.rs/cargo/reference/build-script/intro.html) 一章。 +`build` 用于指定位于项目根目录中的构建脚本,关于构建脚本的更多信息,可以阅读 [构建脚本](https://course.rs/toolchains/cargo/reference/build-script/intro.html) 一章。 ```toml [package] @@ -277,7 +277,7 @@ build = "build.rs" #### links -用于指定项目链接的本地库的名称,更多的信息请看构建脚本章节的 [links](https://course.rs/cargo/reference/build-script/intro.html#links) +用于指定项目链接的本地库的名称,更多的信息请看构建脚本章节的 [links](https://course.rs/toolchains/cargo/reference/build-script/intro.html#links) ```toml [package] @@ -390,9 +390,9 @@ maintenance = { status = "..." } ## [dependencies] -在[之前章节](http://course.rs/cargo/reference/specify-deps.html)中,我们已经详细介绍过 `[dependencies]` 、 `[dev-dependencies]` 和 `[build-dependencies]`,这里就不再赘述。 +在[之前章节](http://course.rs/toolchains/cargo/reference/specify-deps.html)中,我们已经详细介绍过 `[dependencies]` 、 `[dev-dependencies]` 和 `[build-dependencies]`,这里就不再赘述。 ## [profile.*] -该部分可以对编译器进行配置,例如 debug 和优化,在后续的[编译器优化](http://course.rs/cargo/reference/profiles.html)章节有详细介绍。 +该部分可以对编译器进行配置,例如 debug 和优化,在后续的[编译器优化](http://course.rs/toolchains/cargo/reference/profiles.html)章节有详细介绍。 diff --git a/src/cargo/reference/package-id.md b/src/toolchains/cargo/reference/package-id.md similarity index 100% rename from src/cargo/reference/package-id.md rename to src/toolchains/cargo/reference/package-id.md diff --git a/src/cargo/reference/profile.md b/src/toolchains/cargo/reference/profile.md similarity index 100% rename from src/cargo/reference/profile.md rename to src/toolchains/cargo/reference/profile.md diff --git a/src/cargo/reference/profiles.md b/src/toolchains/cargo/reference/profiles.md similarity index 97% rename from src/cargo/reference/profiles.md rename to src/toolchains/cargo/reference/profiles.md index 9c407225..52c0681a 100644 --- a/src/cargo/reference/profiles.md +++ b/src/toolchains/cargo/reference/profiles.md @@ -49,7 +49,7 @@ lto = true cargo build --profile release-lto ``` -与默认的 profile 相同,自定义 profile 的编译结果也存放在 [`target/`](https://course.rs/cargo/guide/build-cache.html) 下的同名目录中,例如 `--profile release-lto` 的输出结果存储在 `target/release-lto` 中。 +与默认的 profile 相同,自定义 profile 的编译结果也存放在 [`target/`](https://course.rs/toolchains/cargo/guide/build-cache.html) 下的同名目录中,例如 `--profile release-lto` 的输出结果存储在 `target/release-lto` 中。 ## 选择 profile @@ -257,7 +257,7 @@ codegen-units = 256 opt-level = 3 ``` -这里的 `package` 名称实际上是一个 [`Package ID`](https://course.rs/cargo/reference/package-id.html),因此我们还可以通过版本号来选择: `[profile.dev.package."foo:2.1.0"]`。 +这里的 `package` 名称实际上是一个 [`Package ID`](https://course.rs/toolchains/cargo/reference/package-id.html),因此我们还可以通过版本号来选择: `[profile.dev.package."foo:2.1.0"]`。 如果要为所有依赖包重写(不包括工作空间的成员): diff --git a/src/cargo/reference/publishing-on-crates.io.md b/src/toolchains/cargo/reference/publishing-on-crates.io.md similarity index 84% rename from src/cargo/reference/publishing-on-crates.io.md rename to src/toolchains/cargo/reference/publishing-on-crates.io.md index d10b4ce3..395767d2 100644 --- a/src/cargo/reference/publishing-on-crates.io.md +++ b/src/toolchains/cargo/reference/publishing-on-crates.io.md @@ -22,14 +22,14 @@ $ cargo login abcdefghijklmnopqrstuvwxyz012345 在发布之前,**确保** `Cargo.toml` 中以下字段已经被设置: -- [license 或 license-file](https://course.rs/cargo/reference/manifest.html#license和license-file) -- [description](https://course.rs/cargo/reference/manifest.html#description) -- [homepage](https://course.rs/cargo/reference/manifest.html#homepage) -- [documentation](https://course.rs/cargo/reference/manifest.html#documentation) -- [repository](https://course.rs/cargo/reference/manifest.html#repository) -- [readme](https://course.rs/cargo/reference/manifest.html#readme) +- [license 或 license-file](https://course.rs/toolchains/cargo/reference/manifest.html#license和license-file) +- [description](https://course.rs/toolchains/cargo/reference/manifest.html#description) +- [homepage](https://course.rs/toolchains/cargo/reference/manifest.html#homepage) +- [documentation](https://course.rs/toolchains/cargo/reference/manifest.html#documentation) +- [repository](https://course.rs/toolchains/cargo/reference/manifest.html#repository) +- [readme](https://course.rs/toolchains/cargo/reference/manifest.html#readme) -你还可以设置[关键字](https://course.rs/cargo/reference/manifest.html#keywords)和[类别](https://course.rs/cargo/reference/manifest.html#categories)等元信息,让包更容易被其他人搜索发现,虽然它们不是必须的。 +你还可以设置[关键字](https://course.rs/toolchains/cargo/reference/manifest.html#keywords)和[类别](https://course.rs/toolchains/cargo/reference/manifest.html#categories)等元信息,让包更容易被其他人搜索发现,虽然它们不是必须的。 如果你发布的是一个依赖库,那么你可能需要遵循相关的[命名规范](https://course.rs/practice/naming.html)和 [API Guidlines](https://rust-lang.github.io/api-guidelines/). @@ -55,7 +55,7 @@ $ cargo publish --dry-run $cargo package --list ``` -当打包时,Cargo 会自动根据版本控制系统的配置来忽略指定的文件,例如 `.gitignore`。除此之外,你还可以通过 [`exclude`](https://course.rs/cargo/reference/manifest.html#exclude和include) 来排除指定的文件: +当打包时,Cargo 会自动根据版本控制系统的配置来忽略指定的文件,例如 `.gitignore`。除此之外,你还可以通过 [`exclude`](https://course.rs/toolchains/cargo/reference/manifest.html#exclude和include) 来排除指定的文件: ```toml [package] @@ -91,7 +91,7 @@ $ cargo pulish 绝大多数时候,我们并不是在发布新包,而是发布已经上传过的包的新版本。 -为了实现这一点,只需修改 `Cargo.toml` 中的 [`version`](https://course.rs/cargo/reference/manifest.html#version) 字段 ,但需要注意:**版本号需要遵循 `semver` 规则**。 +为了实现这一点,只需修改 `Cargo.toml` 中的 [`version`](https://course.rs/toolchains/cargo/reference/manifest.html#version) 字段 ,但需要注意:**版本号需要遵循 `semver` 规则**。 然后再次使用 `cargo publish` 就可以上传新的版本了。 diff --git a/src/cargo/reference/specify-deps.md b/src/toolchains/cargo/reference/specify-deps.md similarity index 97% rename from src/cargo/reference/specify-deps.md rename to src/toolchains/cargo/reference/specify-deps.md index 5dc239a2..5197ee66 100644 --- a/src/cargo/reference/specify-deps.md +++ b/src/toolchains/cargo/reference/specify-deps.md @@ -145,7 +145,7 @@ regex = { git = "https://github.com/rust-lang/regex", branch = "next" } **因此不要依赖锁定来完成版本的控制,而应该老老实实的在 `Cargo.toml` 小心配置你希望使用的版本。** -如果访问的是私有仓库,你可能需要授权来访问该仓库,可以查看[这里](https://course.rs/cargo/git-auth.html)了解授权的方式。 +如果访问的是私有仓库,你可能需要授权来访问该仓库,可以查看[这里](https://course.rs/toolchains/cargo/git-auth.html)了解授权的方式。 #### 通过路径引入本地依赖包 @@ -223,7 +223,7 @@ openssl = "1.0.1" 如果你想要知道 `cfg` 能够作用的目标,可以在终端中运行 `rustc --print=cfg` 进行查询。当然,你可以指定平台查询: `rustc --print=cfg --target=x86_64-pc-windows-msvc`,该命令将对 `64bit` 的 Windows 进行查询。 -聪明的同学已经发现,这非常类似于条件依赖引入,那我们是不是可以根据自定义的条件来决定是否引入某个依赖呢?具体答案参见后续的 [feature](https://course.rs/cargo/reference/features.html) 章节。这里是一个简单的示例: +聪明的同学已经发现,这非常类似于条件依赖引入,那我们是不是可以根据自定义的条件来决定是否引入某个依赖呢?具体答案参见后续的 [feature](https://course.rs/toolchains/cargo/reference/features.html) 章节。这里是一个简单的示例: ```toml [dependencies] @@ -310,7 +310,7 @@ default-features = false # 不要包含默认的 features,而是通过下面 features = ["secure-password", "civet"] ``` -更多的信息参见 [Features 章节](https://course.rs/cargo/reference/features.html) +更多的信息参见 [Features 章节](https://course.rs/toolchains/cargo/reference/features.html) ## 在 Cargo.toml 中重命名依赖 diff --git a/src/cargo/reference/workspaces.md b/src/toolchains/cargo/reference/workspaces.md similarity index 91% rename from src/cargo/reference/workspaces.md rename to src/toolchains/cargo/reference/workspaces.md index e3d7b751..976798f0 100644 --- a/src/cargo/reference/workspaces.md +++ b/src/toolchains/cargo/reference/workspaces.md @@ -54,7 +54,7 @@ exclude = ["crates/proc_macro_test/imp"] 工作空间的几个关键点在于: - 所有的 `package` 共享同一个 `Cargo.lock` 文件,该文件位于工作空间的根目录中 -- 所有的 `package` 共享同一个[输出目录](https://course.rs/cargo/guide/build-cache.html),该目录默认的名称是 `target` ,位于工作空间根目录下 +- 所有的 `package` 共享同一个[输出目录](https://course.rs/toolchains/cargo/guide/build-cache.html),该目录默认的名称是 `target` ,位于工作空间根目录下 - 只有工作空间根目录的 `Cargo.toml` 才能包含 `[patch]`, `[replace]` 和 `[profile.*]`,而成员的 `Cargo.toml` 中的相应部分将被自动忽略 ## [workspace] @@ -67,7 +67,7 @@ members = ["member1", "path/to/member2", "crates/*"] exclude = ["crates/foo", "path/to/other"] ``` -若某个本地依赖包是通过 [`path`](https://course.rs/cargo/reference/specify-deps.html#通过路径引入本地依赖包) 引入,且该包位于工作空间的目录中,则该包自动成为工作空间的成员。 +若某个本地依赖包是通过 [`path`](https://course.rs/toolchains/cargo/reference/specify-deps.html#通过路径引入本地依赖包) 引入,且该包位于工作空间的目录中,则该包自动成为工作空间的成员。 剩余的成员需要通过 `workspace.members` 来指定,里面包含了各个成员所在的目录(成员目录中包含了 Cargo.toml )。 @@ -116,7 +116,7 @@ default-members = ["path/to/member2", "path/to/member3/foo"] ## workspace.metadata -与 [package.metadata](https://course.rs/cargo/reference/manifest.html#metadata) 非常类似,`workspace.metadata` 会被 `Cargo` 自动忽略,就算没有被使用也不会发出警告。 +与 [package.metadata](https://course.rs/toolchains/cargo/reference/manifest.html#metadata) 非常类似,`workspace.metadata` 会被 `Cargo` 自动忽略,就算没有被使用也不会发出警告。 这个部分可以用于让工具在 `Cargo.toml` 中存储一些工作空间的配置元信息。例如: diff --git a/src/toolchains/intro.md b/src/toolchains/intro.md new file mode 100644 index 00000000..092c4695 --- /dev/null +++ b/src/toolchains/intro.md @@ -0,0 +1,5 @@ +# Rust 工具链指南 + +工具链是一门语言的第二灵魂,如果问一个 C++ 程序员,他最羡慕 Rust 什么,那答案很可能就是 Rust 的工具链。 + +而其中最主要的就是 Cargo 包管理工具,下面让我们从它开始。 \ No newline at end of file diff --git a/内容变更记录.md b/内容变更记录.md index c76d7d35..8fd108c2 100644 --- a/内容变更记录.md +++ b/内容变更记录.md @@ -1,8 +1,22 @@ # ChangeLog 记录一些值得注意的变更。 +## 2022-03-18 + +- 新增章节: [不错的unsafe队列-基本操作](http://localhost:3000/too-many-lists/unsafe-queue/basics.html) +- 新增章节: [不错的unsafe队列-使用miri](http://localhost:3000/too-many-lists/unsafe-queue/miri.html) + +## 2022-03-17 + +- 将`原生指针`更名为`裸指针` +- 新增章节: [不错的unsafe队列-数据布局](https://course.rs/too-many-lists/unsafe-queue/layout.html) +- 新增章节: [deque-迭代器](https://course.rs/too-many-lists/deque/iterator.html) +- 新增章节: [deque-最终代码](https://course.rs/too-many-lists/deque/final-code.html) + ## 2022-03-16 +- 新增章节: [deque-基本操作的对称镜像](https://course.rs/too-many-lists/deque/symmetric.html) +- 新增章节: [deque-Peek](https://course.rs/too-many-lists/deque/peek.html) - 新增章节: [deque-数据布局和基本操作](https://course.rs/too-many-lists/deque/layout.html) ## 2022-03-14 @@ -55,15 +69,15 @@ ## 2022-03-03 -- 新增章节: [Cargo - 构建脚本示例](https://course.rs/cargo/reference/build-script/examples.html) +- 新增章节: [Cargo - 构建脚本示例](https://course.rs/toolchains/cargo/reference/build-script/examples.html) ## 2022-03-02 -- 新增章节: [Cargo - 构建脚本](https://course.rs/cargo/reference/build-script/intro.html) +- 新增章节: [Cargo - 构建脚本](https://course.rs/toolchains/cargo/reference/build-script/intro.html) ## 2022-02-28 -- 新增章节: [Cargo - 发布到crates.io](https://course.rs/cargo/reference/publishing-on-crates.io.html) +- 新增章节: [Cargo - 发布到crates.io](https://course.rs/toolchains/cargo/reference/publishing-on-crates.io.html) - 新增内容:[结构体 - #[derive(Debug)]](https://course.rs/basic/compound-type/struct.html#使用-derivedebug-来打印结构体的信息) ## 2022-02-27