Merge branch 'main' into index

pull/784/head
Allan Downey 3 years ago
commit 8262341177

@ -1,52 +1,138 @@
# Rust语言圣经 (The Course)
<h1 align="center">Rust语言圣经</h1>
- 在线阅读
- 官方: [https://course.rs](https://course.rs)
- 知乎: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017)
- Rust语言社区: QQ群 1009730433
> 学习 Rust 光看书不够,精心设计的习题和项目实践可以让你事半功倍。[Rust By Practice](https://github.com/sunface/rust-by-practice) 是本书的配套习题和实践,覆盖了 easy to hard 各个难度,满足大家对 Rust 的所有期待。
>
> [Rust 语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态。
>
> Rust 优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust [Fancy Rust](https://github.com/sunface/fancy-rust) 能带给你全新的体验和选择。
<div align="center">
<img src="https://github.com/sunface/rust-course/blob/main/assets/banner.jpg?raw=true">
</div>
<div align="center">
[![studyrut](https://img.shields.io/badge/study-rust-orange)](https://github.com/studyrs) [![Stars Count](https://img.shields.io/github/stars/sunface/rust-course?style=flat)](https://github.com/sunface/rust-course/stargazers)
[![](https://img.shields.io/github/issues-pr-closed-raw/sunface/rust-course.svg?style=flat)](https://github.com/sunface/rust-course/issues)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/ines/spacy-course/master)
<!-- <a href="https://www.zhihu.com/column/c_1452781034895446017">
<img alt="Sunface | 知乎" height="20px" width="20px" src="https://github.com/sunface/rust-course/blob/main/assets/zhihu.jpg">
</a> -->
</div>
## 教程简介
- 在线阅读: https://course.rs
**`Rust语言圣经`**涵盖从**入门到精通**所需的 Rust 知识,目录及内容都经过深思熟虑的设计,同时语言生动幽默,行文流畅自如,摆脱技术书籍常有的机器味和晦涩感。
在 Rust 基础教学的同时,我们还提供了(部分):
- **深入度**,在基础教学的同时,提供了深入剖析。浅尝辄止并不能让我们站上紫禁之巅
- **性能优化**,选择 Rust就意味着要追求性能因此你需要体系化地了解性能优化
- **专题**,将 Rust 高级内容通过专题的形式一一呈现,内容内聚性极强
- **难点和错误索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕
- **场景化模版**,程序员上网查询如何操作文件是常事,没有人能记住所有代码,场景化模版可解君忧
总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。
- **专题内容**,将 Rust 高级内容通过专题的形式一一呈现内容内聚性极强例如性能优化、手把手实现链表、Cargo和Tokio使用指南、async异步编程、标准库解析、WASM等等
## ❤️ 开源
本书是完全开源的,但是并不意味着质量上的妥协,这里的每一个章节都花费了大量的心血和时间才能完成,为此牺牲了陪伴家人、日常娱乐的时间,虽然我们并不后悔,但是如果能得到读者您的鼓励,我们将感激不尽。
- **内容索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕
既然是开源,那最大的鼓励不是 money而是 star:) **如果大家觉得这本书作者真的用心了,就帮我们点一个 🌟 吧,这将是我们继续前行最大的动力**
- **规避陷阱和对抗编译器**,只有真的上手写过一长段时间 Rust 项目,才知道该如何规避常见的陷阱以及解决一些难搞的编译器错误,而本书将帮助你大大缩短这个过程,提前规避这些问题
> 在开源版权上,我们选择了 [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 和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,望理解。
- **[Cookbook](https://rusty.rs)**,涵盖多个应用场景的实战代码片段,程序员上网查询文件操作、正则解析、数据库操作是常事,没有人能记住所有代码,而 Cookbook 可解君忧Ctrl + C/V 走天下
- **[配套练习题](https://github.com/sunface/rust-by-practice)**,像学习一门大学课程一样学习 Rust 是一种什么感觉?*Rust语言圣经 + Rust语言实战* 双剑合璧,给你最极致的学习体验
总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。
## 贡献者
非常感谢本教程的所有贡献者们,正是有了你们,才有了现在的高质量 Rust 教程!
- [@AllanDowney](https://github.com/AllanDowney)
- [@JesseAtSZ](https://github.com/JesseAtSZ)
- [@1132719438](https://github.com/1132719438)
- [@Mintnoii](https://github.com/Mintnoii)
- [@Rustln](https://github.com/rustln)
## 🏅 贡献者
非常感谢本教程的[所有贡献者](https://github.com/sunface/rust-course/graphs/contributors),正是有了你们,才有了现在的高质量 Rust 教程!
<br />
**🏆 贡献榜前三**(根据难易度、贡献次数、活跃度综合评定):
<table>
<tr>
<td align="center">
<a href="https://github.com/sunface">
<img src="https://avatars.githubusercontent.com/u/7036754?v=4?s=100" width="160px" alt=""/>
<br />
<sub><b>Sunface 🥇</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/AllanDowney">
<img src="https://avatars.githubusercontent.com/u/82752697?v=4?s=100" width="160px" alt=""/>
<br />
<sub><b>AllanDowney 🥈</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/JesseAtSZ">
<img src="https://avatars.githubusercontent.com/u/35264598?v=4?s=100" width="160px" alt=""/>
<br />
<sub><b>JesseAtSZ 🥉</b></sub>
</a>
</td>
</tr>
</table>
<br />
🏅 核心贡献者:
<table>
<tr>
<td align="center">
<a href="https://github.com/1132719438">
<img src="https://avatars.githubusercontent.com/u/10138791?v=4?s=100" width="100px" alt=""/>
<br />
<sub><b>1132719438</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/zongzi531">
<img src="https://avatars.githubusercontent.com/u/22429236?v=4?s=100" width="100px" alt=""/>
<br />
<sub><b>zongzi531</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Mintnoii">
<img src="https://avatars.githubusercontent.com/u/30466018?v=4?s=100" width="100px" alt=""/>
<br />
<sub><b>Mintnoii</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Rustln">
<img src="https://avatars.githubusercontent.com/u/100085326?v=4?s=100" width="100px" alt=""/>
<br />
<sub><b>Rustln</b></sub>
</a>
</td>
</tr>
</table>
## 创作感悟
截至目前Rust语言圣经已写了 170 余章110 余万字,历经 800 多个小时,每一个章节都是手动写就,没有任何机翻和质量上的妥协( 相信深入阅读过的读者都能体会到这一点 )。
曾经有读者问过 "这么好的书为何要开源,而不是出版?",原因很简单:**只有完全开源才能完美地呈现出我想要的教学效果**。
总之Rust 要在国内真正发展起来,必须得有一些追逐梦想的人在做着不计付出的事情,而我希望自己能贡献一份微薄之力。
但是要说完全无欲无求,那也是不可能的,看到项目多了一颗 🌟,那感觉...棒极了,因为它代表了读者的认可和称赞。
你们用指尖绘制的星空,那里繁星点点,每一颗都在鼓励着怀揣着开源梦想的程序员披荆斩棘、不断前行,不夸张的说,没有你们,开源世界就没有星光,自然也就不会有今天的开源盛世。
因此,**我恳请大家,如果觉得书还可以,就在你的指尖星空绘制一颗新的 🌟,指引我们继续砥砺前行**。这个人世间,因善意而美好。
最后,能通过开源在茫茫人海中与大家相识,这感觉真好 :D
## 开源协议
在开源版权上,我们选择了 [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 和阅读,但是**不能私下修改后再包装分发**,如果有这方面的需求,请联系我们,望理解。
尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~
## 借鉴的书籍
@ -59,5 +145,14 @@
因为它们绝大部分是支持 APACHE + MIT 双协议的,因此我们选择了遵循其中的 MIT 协议,并在这里统一对借鉴的书籍进行说明。
## Rust语言社区
QQ群 1009730433 欢迎大家加入,一起 happy一起进步。
## 社区 & 读者交流
- 知乎: [孙飞 Sunface](https://www.zhihu.com/people/iSunface)
- **StudyRust** 社区
- QQ群 `1009730433`,用于日常技术交流
- 微信公众号: 搜索 `studyrust` 或扫描下面的二维码关注公众号 `Rust语言中文网`
<img src="https://github.com/sunface/rust-course/blob/main/assets/studyrust公众号.png?raw=true" />

@ -1,422 +0,0 @@
> 词汇表是从https://github.com/rust-lang-cn/english-chinese-glossary-of-rust fork而来原因是在部分词汇的翻译上存在不同的意见欢迎大家开issue讨论或者提交pr
# Rust 语言术语中英文对照表
English 英文 | Chinese 中文 | Note 备注
------------------------------- |----------------------------- |----------
**A** | |
Abstract Syntax Tree | 抽象语法树 |
ABI | 应用程序二进制接口 | Application Binary Interface 缩写
accumulator | 累加器 |
accumulator variable | 累加器变量 |
ahead-of-time compiled | 预编译 |
ahead-of-time compiled language | 预编译语言 |
algebraic data types(ADT) | 代数数据类型 |
alias | 别名 |
aliasing | 别名使用 | 参见 [Wikipedia](https://en.wikipedia.org/wiki/Pointer_aliasing)
angle brackets | 尖括号,“&lt;”和“&gt;” |
annotate | 标注,注明(动词) |
annotation | 标注,注明(名词) |
ARC | 原子引用计数器 | Atomic Referecne Counter
anonymity | 匿名 |
argument | 参数,实参,实际参数 | 不严格区分的话, argument参数<br> parameter参量可以互换地使用
argument type | 参数类型 |
assignment | 赋值 |
associated functions | 关联函数 |
associated items | 关联项 |
associated types | 关联类型 |
asterisk | 星号(\*) |
atomic | 原子的 |
attribute | 属性 |
automated building | 自动构建 |
automated test | 自动测试,自动化测试 |
**B** | |
baroque macro | 巴洛克宏 |
benchmark | 基准 |
binary | 二进制的 |
binary executable | 二进制的可执行文件 |
bind | 绑定 |
block | 语句块,代码块 |
boolean | 布尔型,布尔值 |
borrow check | 借用检查 |
borrower | 借用者,借入者 |
borrowed | 借用的 |
borrowing | 借用 |
bound | 约束,限定,限制 | 此词和 constraint 意思相近,<br>constraint 在 C# 语言中翻译成“约束”
box | 箱子,盒子,装箱类型 | 一般不译,作动词时翻译成“装箱”,<br>具有所有权的智能指针
boxed | 装箱,装包 |
boxing | 装箱,装包 |
brace | 大括号,“{”或“}” |
breaking changes | 破坏性变更 |
buffer | 缓冲区 |
build | 构建 |
builder pattern | 创建者模式 |
**C** | |
call | 调用 |
caller | 调用者 |
capacity | 容量 |
capture | 捕获 |
cargo | (Rust 包管理器,不译) | 该词作名词时意思是“货物”,<br>作动词时意思是“装载货物”
cargo-fy | Cargo 化,使用 Cargo 创建项目 |
case analysis | 事例分析 |
cast | 类型转换,转型 |
casting | 类型转换 |
chaining method call | 链式方法调用 |
channel | 信道,通道 |
closure | 闭包 |
coercion | 强制类型转换,强制转换 | coercion 原意是“强制,胁迫”
collection | 集合 | 参见 [Wikipedia](https://zh.wikipedia.org/wiki/%E9%9B%86%E5%90%88_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)) |
combinator | 组合算子,组合器 |
comma | 逗号,“,” |
command | 命令 |
command line | 命令行 |
comment | 注释 |
compile | 编译(动词) |
compile time | 编译期,编译期间,编译时 |
compilation | 编译(名词) |
compilation unit | 编译单元 |
compiler | 编译器 |
compiler intrinsics | 编译器固有功能 |
compound | 复合(类型,数据) |
concurrency | 并发 |
conditional compilation | 条件编译 |
configuration | 配置 |
constant | 常量 |
constant raw pointer | 原生常量指针 |
constructor | 构造器 |
consumer | 消费者 |
container | 容器 |
container type | 容器类型 |
convert | 转换,转化,转 |
copy | 复制,拷贝 |
crate | 包 | crate 是 Rust 的基本编译单元
crate root | 包根 | 别拍我,我知道很奇葩
curly braces | 大括号,包含“{”和“}” |
custom type | 自定义类型 |
**D** | |
dangling pointer | 悬垂指针 | use after free 在释放后使用
data race | 数据竞争 |
dead code | 死代码,无效代码,不可达代码 |
deallocate | 释放,重新分配 |
declare | 声明 |
deep copy | 深拷贝,深复制 |
dependency | 依赖 |
deref coercions | 解引用强制转换 |
dereference | 解引用 | Rust 文章中有时简写为 Deref
derive | 派生 |
designator | 指示符 |
destruction | 销毁,毁灭 |
destructor | 析构器,析构函数 |
destructure | 解构 |
destructuring | 解构,解构赋值 |
desugar | 脱糖 |
diverge function | 发散函数 |
device drive | 设备驱动 |
directory | 目录 |
dispatch | 分发 |
diverging functions | 发散函数 |
documentation | 文档 |
dot operator | 点运算符 |
DST | 动态大小类型 | dynamic sized type一般不译<br>使用英文缩写形式
dynamic language | 动态类型语言 |
dynamic trait type | 动态特质类型 |
**E** | |
enum variant | 枚举成员 |
enumeration | 枚举 |
encapsulation | 封装 |
equality test | 相等测试 |
elision | 省略 |
exhaustiveness checking | 穷尽性检查,无遗漏检查 |
executor | 执行器 |
expression | 表达式 |
expression-oriented language | 面向表达式的语言 |
explicit | 显式 |
explicit discriminator | 显式的辨别值 |
explicit type conversion | 显式类型转换 |
extension | 扩展名 |
extern | 外,外部 | 作关键字时不译
**F** | |
fat pointer | 宽指针 |
Feature | 暂时不译 | 在Rust中主要用于Cargo feature该词
feature gate | 功能开关 |
field | 字段 |
field-level mutability | 字段级别可变性 |
file | 文件 |
fmt | 格式化,是 format 的缩写 |
formatter | 格式化程序,格式化工具,格式器|
floating-point number | 浮点数 |
flow control | 流程控制 |
Foreign Function InterfaceFFI| 外部语言函数接口 |
fragment specifier | 片段分类符 |
free variables | 自由变量 |
freeze | 冻结 |
function | 函数 |
function declaration | 函数声明 |
functional | 函数式 |
**G** | |
garbage collector | 垃圾回收 |
generalize | 泛化,泛型化 |
generator | 生成器 |
generic | 泛型 |
generic type | 泛型类型 |
getter | 读访问器 |
growable | 可增长的 |
guard | 守卫 |
**H** | |
handle error | 句柄错误 |
hash | 哈希,哈希值,散列 |
hash map | 散列映射,哈希表 |
heap | 堆 |
hierarchy | 层次,分层,层次结构 |
higher rank lifetime | 高阶生命周期 |
higher rank trait bound | 高阶特质约束 |
higher rank type | 高阶类型 |
hygiene | 卫生 |
hygienic macro system | 卫生宏系统 |
**I** | |
ICE | 编译内部错误 | internal compiler error 的缩写
immutable | 不可变的 |
implement | 实现 |
implementor | 实现者 |
implicit | 隐式 |
implicit discriminator | 隐式的辨别值 |
implicit type conversion | 隐式类型转换 |
import | 导入 |
in assignment | 在赋值(语句) |
index | 索引 | 英语复数形式indices
infer | 推导(动词) |
inference | 推导(名词) |
inherited mutability | 承袭可变性 |
inheritance | 继承 |
integrated development <br>environment(IDE) | 集成开发环境 | 中文著作中通常直接写成 IDE
integration-style test | 集成测试 |
interior mutability | 内部可变性 |
installer | 安装程序,安装器 |
instance | 实例 |
instance method | 实例方法 |
integer | 整型,整数 |
interact | 相互作用,相互影响 |
interior mutability | 内部可变性 |
intrinsic | 固有的 |
invoke | 调用 |
item | 项,条目,项目 |
iterate | 重复 |
iteration | 迭代 |
iterator | 迭代器 |
iterator adaptors | 迭代器适配器 |
iterator invalidation | 迭代器失效 |
**L** | |
local variables | 局部变量 |
LHS | 左操作数 | left-hand side 的非正式缩写,<br>与 RHS 相对
lender | 借出者 |
library | 库 |
lifetime | 生命周期 |
lifetime elision | 生命周期消除 |
link | 链接 |
linked-list | 链表 |
lint | 代码静态分析 | Lint, or a linter, is a static code analysis tool used to flag programming errors, bugs, stylistic errors and suspicious constructs |
list | 列表 |
listener | 监听器 |
literal | 数据,常量数据,字面值,字面量,<br>字面常量,字面上的 | 英文意思:字面意义的(内容)
LLVM | (不译) | Low Level Virtual Machine 的缩写,<br>是构建编译器的系统
loop | 循环 | 作关键字时不译
low-level code | 底层代码 |
low-level language | 底层语言 |
l-value | 左值 |
**M** | |
main function | main 函数,主函数 |
macro | 宏 |
map | 映射 | 一般不译
match guard | 匹配守卫 |
memory | 内存 |
memory leak | 内存泄露 |
memory safe | 内存安全 |
meta | 原则,元 |
metadata | 元数据 |
metaprogramming | 元编程 |
metavariable | 元变量 |
method call syntax | 方法调用语法 |
method chaining | 方法链 |
method definition | 方法定义 |
modifier | 修饰符 |
module | 模块 |
monomorphization | 单态 | mono: one, morph: form
move | 移动,转移 | 按照 Rust 所规定的内容,<br>英语单词 transfer 的意思<br>比 move 更贴合实际描述<br>参考:[Rust by Example](http://rustwiki.org/rust-by-example/scope/move.html)
move semantics | 移动语义 |
mutability | 可变性 |
mutable | 可变 |
mutable reference | 可变引用 |
multiple bounds | 多重约束 |
mutiple patterns | 多重模式 |
**N** | |
naming | 命名 |
nest | 嵌套 |
Nightly Rust | Rust 开发版 | nightly本意是“每夜每天晚上”<br>指代码每天都更新
NLL | 非词法生命周期 | non lexical lifetime 的缩写,<br>一般不译
non-copy type | 非复制类型 |
non-generic | 非泛型 |
no-op | 空操作,空运算 | (此词出现在类型转换章节中)
non-commutative | 非交换的 |
non-scalar cast | 非标量转换 |
notation | 符号,记号 |
number type | 数据类型
numeric | 数值,数字 |
**O** | |
optimization | 优化 |
out-of-bounds accessing | 越界访问 |
orphan rule | 孤儿规则 |
overflow | 溢出,越界 |
own | 占有,拥有 |
owned | 所拥有的 |
owner | 所有者,拥有者 |
ownership | 所有权 |
**P** | |
package | 不翻译 |
panic | 异常、致命错误、不译 | 在 Rust 中用于不可恢复的错误处理跟其它语言的exception类似
parallelism | 并行 |
parameter | 参数 |
parametric polymorphism | 参数多态 |
parent scope | 父级作用域 |
parentheses | 小括号,包括“(”和“)” |
parse | 分析,解析 |
parser | (语法)分析器,解析器 |
pattern | 模式 |
pattern match | 模式匹配 |
phantom type | 虚类型,虚位类型 | phantom 相关的专有名词:<br>phantom bug 幻影指令<br>phantom power 幻象电源<br>参见:[Haskell](https://wiki.haskell.org/Phantom_type)、[Haskell/Phantom_type](https://en.wikibooks.org/wiki/Haskell/Phantom_types)、<br>[Rust/Phantom](http://rustwiki.org/rust-by-example/generics/phantom.html)、[stdlib/PhantomData](https://doc.rust-lang.org/std/marker/struct.PhantomData.html)
platform | 平台 |
polymorphism | 多态 |
powershell |(不译) | Windows 系统的一种命令行外壳程序<br>和脚本环境
possibility of absence | 不存在的可能性 |
precede | 预先?,在...发生(或出现) |
prelude |(不译) | 预先导入模块,英文本意:序曲,前奏
primitive types | 原生类型,基本类型,简单类型 |
print | 打印 |
process | 进程 |
procedural macros | 过程宏,程序宏 |
project | 项目,工程 |
prototype | 原型 |
**R** | |
race condition | 竞态条件 |
RAII | 资源获取即初始化(一般不译) | resource acquisition is initialization 的缩写
range | 区间,范围 |
range expression | 区间表达式 |
raw identifier | 原生标识符 |
raw pointer | 原生指针,裸指针 |
RC | 引用计数 | reference counted
reader | 读取器 |
reader/writer | 读写器 |
recursive macro | 递归宏 |
reference | 引用 |
reference cycle | 引用循环 |
release | 发布 |
resource | 资源 |
resource leak | 资源泄露 |
RHS | 右操作数 | right-hand side 的非正式缩写,<br>与 LHS 相对
root directory | 根目录 |
runtime | 运行时 |
runtime behavior | 运行时行为 |
runtime overhead | 运行时开销 |
Rust | (不译) | 一种编程语言
Rustacean | (不译) | 编写 Rust 的程序员或爱好者的通称
rustc | (不译) | Rust 语言编译器
r-value | 右值 |
**S** | |
scalar | 标量,数量 |
schedule | 调度 |
scope | 作用域 |
screen | 屏幕 |
script | 脚本 |
semicolon | 分号,“;” |
self | 自身,作关键字时不译 |
setter | 写访问器 |
shadow | 遮蔽,隐蔽,隐藏,覆盖 |
shallow copy | 浅拷贝,浅复制 |
signature | 标记 |
slice | 切片 |
snake case | 蛇形命名 | 参见:[Snake case](https://en.wikipedia.org/wiki/Snake_case)
source file | 源文件 |
source code | 源代码 |
specialization | 泛型特化 |
square | 平方,二次方,二次幂 |
square brackets | 中括号,“[”和“]” |
src | (不译) | source 的缩写,指源代码
stack | 栈 |
stack unwind | 栈解开、栈展开 |
statement | 语句 |
statically allocated | 静态分配 |
statically allocated string | 静态分配的字符串 |
statically dispatch | 静态分发 |
static method | 静态方法 |
string | 字符串 |
string literal | 字符串常量 |
string slices | 字符串切片 |
stringify | 字符串化 |
subscript notation | 下标 |
sugar | 糖 |
super | 父级,作关键字时不译 |
syntax context | 语法上下文 |
systems programming language | 系统级编程语言 |
**T** | |
tagged union | 标记联合 |
target triple | 多层次指标,三层/重 指标/目标 | triple 本义是“三”,但此处虚指“多”,<br>此词翻译需要更多讨论
terminal | 终端 |
testing | 测试 |
testsuit | 测试套件 |
the least significant bit (LSB) | 最低数字位 |
the most significant bit (MSB) | 最高数字位 |
thread | 线程 |
TOML | (不译) | Tom's Obvious, Minimal Language <br>的缩写,一种配置语言
token tree | 令牌树? | 待进一步斟酌
trait | 特征 | 其字面上有“特性,特征”之意
trait bound | 特征约束 | bound 有“约束,限制,限定”之意
trait object | 特征对象 |
transmute | (不译) | 其字面上有“变化,变形,变异”之意,<br>不作翻译
trivial | 平凡的 |
troubleshooting | 疑难解答,故障诊断,<br>故障排除,故障分析 |
tuple | 元组 |
turbofish | 双冒号`::` 难以翻译,所以直接用形译法
two's complement | 补码,二补数 |
two-word object | 双字对象 |
type annotation | 类型标注 |
type erasure | 类型擦除 |
type inference | 类型推导 |
type inference engine | 类型推导引擎 |
type parameter | 类型参量 |
type placeholder | 类型占位符 |
type signature | 类型标记 |
**U** | |
undefined behavior | 未定义行为 |
uninstall | 卸载 |
unit-like struct | 类单元结构体 |
unit struct | 单元结构体 |
"unit-style" tests | 单元测试 |
unit test | 单元测试 |
unit type | 单元类型 |
universal function call syntax <br>(UFCS) | 通用函数调用语法 |
unsized types | 不定长类型 |
unwind | 展开 |
unwrap | 解包 | 暂译!
**V** | |
variable binding | 变量绑定 |
variable shadowing | 变量遮蔽,变量隐蔽,<br>变量隐藏,变量覆盖 |
variable capture | 变量捕获 |
variant | 变量 |
vector | (动态数组,一般不译) | vector 本义是“向量”
visibility | 可见性 |
vtable | 虚表 |
**W** | |
where clause | where 子句where 从句where 分句 | 在数据库的官方手册中多翻译成“子句”,英语语法中翻译成“从句”
workspace | 工作空间 |
wrap | 包装 | 暂译!
wrapped | 装包 |
wrapper | 装包 |
writer | 写入器 |
**Y** | |
yield | 产生(收益、效益等),产出,提供|
**Z** | |
zero-cost abstractions | 零开销抽象 |
zero-width space(ZWSP) | 零宽空格 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,158 @@
var initAll = function () {
var path = window.location.pathname;
if (path.endsWith("/print.html")) {
return;
}
var images = document.querySelectorAll("main img")
Array.prototype.forEach.call(images, function (img) {
img.addEventListener("click", function () {
BigPicture({
el: img,
});
});
});
// Un-active everything when you click it
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.addEventHandler("click", function () {
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.classList.remove("active");
});
el.classList.add("active");
});
});
var updateFunction = function () {
var id = null;
var elements = document.getElementsByClassName("header");
Array.prototype.forEach.call(elements, function (el) {
if (window.pageYOffset >= el.offsetTop) {
id = el;
}
});
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
el.classList.remove("active");
});
Array.prototype.forEach.call(document.getElementsByClassName("pagetoc")[0].children, function (el) {
if (id == null) {
return;
}
if (id.href.localeCompare(el.href) == 0) {
el.classList.add("active");
}
});
};
var pagetoc = document.getElementsByClassName("pagetoc")[0];
var elements = document.getElementsByClassName("header");
Array.prototype.forEach.call(elements, function (el) {
var link = document.createElement("a");
// Indent shows hierarchy
var indent = "";
switch (el.parentElement.tagName) {
case "H1":
return;
case "H3":
indent = "20px";
break;
case "H4":
indent = "40px";
break;
default:
break;
}
link.appendChild(document.createTextNode(el.text));
link.style.paddingLeft = indent;
link.href = el.href;
pagetoc.appendChild(link);
});
updateFunction.call();
// Handle active elements on scroll
window.addEventListener("scroll", updateFunction);
document.getElementById("theme-list").addEventListener("click", function (e) {
var iframe = document.querySelector('.giscus-frame');
if (!iframe) return;
var theme;
if (e.target.className === "theme") {
theme = e.target.id;
} else {
return;
}
// 若当前 mdbook 主题不是 Light 或 Rust ,则将 giscuz 主题设置为 transparent_dark
var giscusTheme = "light"
if (theme != "light" && theme != "rust") {
giscusTheme = "transparent_dark";
}
var msg = {
setConfig: {
theme: giscusTheme
}
};
iframe.contentWindow.postMessage({ giscus: msg }, 'https://giscus.app');
});
pagePath = pagePath.replace("index.md", "");
pagePath = pagePath.replace(".md", "");
if (pagePath.length > 0) {
if (pagePath.charAt(pagePath.length-1) == "/"){
pagePath = pagePath.substring(0, pagePath.length-1)
}
}else {
pagePath = "index"
}
// add vistors count
var ele = document.createElement("div");
ele.setAttribute("align","center");
var count = document.createElement("img")
count.setAttribute("src", "https://visitor-badge.glitch.me/badge?page_id=" + path);
ele.appendChild(count);
var divider =document.createElement("hr")
document.getElementById("giscus-container").appendChild(ele);
document.getElementById("giscus-container").appendChild(divider);
// 选取浏览器默认使用的语言
// const lang = navigator.language || navigator.userLanguage
// 若当前 mdbook 主题为 Light 或 Rust ,则将 giscuz 主题设置为 light
var theme = "transparent_dark";
const themeClass = document.getElementsByTagName("html")[0].className;
if (themeClass.indexOf("light") != -1 || themeClass.indexOf("rust") != -1) {
theme = "light"
}
var script = document.createElement("script")
script.type = "text/javascript";
script.src = "https://giscus.app/client.js";
script.async = true;
script.crossOrigin = "anonymous";
script.setAttribute("data-repo", "sunface/rust-course");
script.setAttribute("data-repo-id", "MDEwOlJlcG9zaXRvcnkxNDM4MjIwNjk=");
script.setAttribute("data-category", "章节评论区");
script.setAttribute("data-category-id", "DIC_kwDOCJKM9c4COQcP");
script.setAttribute("data-mapping", "specific");
script.setAttribute("data-term", pagePath);
script.setAttribute("data-reactions-enabled", "1");
script.setAttribute("data-emit-metadata", "0");
script.setAttribute("data-input-position", "top");
script.setAttribute("data-theme", theme);
// script.setAttribute("data-lang", lang);
// 预先加载评论会更好,这样用户读到那边时,评论就加载好了
// script.setAttribute("data-loading", "lazy");
document.getElementById("giscus-container").appendChild(script);
};
window.addEventListener('load', initAll);

@ -1,33 +0,0 @@
body.light .does_not_compile,
body.light .panics,
body.light .not_desired_behavior,
body.rust .does_not_compile,
body.rust .panics,
body.rust .not_desired_behavior {
background: #fff1f1;
}
body.coal .does_not_compile,
body.coal .panics,
body.coal .not_desired_behavior,
body.navy .does_not_compile,
body.navy .panics,
body.navy .not_desired_behavior,
body.ayu .does_not_compile,
body.ayu .panics,
body.ayu .not_desired_behavior {
background: #501f21;
}
.ferris {
position: absolute;
z-index: 99;
right: 5px;
top: 30px;
width: 10%;
height: auto;
}
.ferris-explain {
width: 100px;
}

@ -1,51 +0,0 @@
var ferrisTypes = [
{
attr: 'does_not_compile',
title: 'This code does not compile!'
},
{
attr: 'panics',
title: 'This code panics!'
},
{
attr: 'unsafe',
title: 'This code block contains unsafe code.'
},
{
attr: 'not_desired_behavior',
title: 'This code does not produce the desired behavior.'
}
]
document.addEventListener('DOMContentLoaded', () => {
for (var ferrisType of ferrisTypes) {
attachFerrises(ferrisType)
}
})
function attachFerrises (type) {
var elements = document.getElementsByClassName(type.attr)
for (var codeBlock of elements) {
var lines = codeBlock.textContent.split(/\r|\r\n|\n/).length - 1;
if (lines >= 4) {
attachFerris(codeBlock, type)
}
}
}
function attachFerris (element, type) {
var a = document.createElement('a')
a.setAttribute('href', 'ch00-00-introduction.html#ferris')
a.setAttribute('target', '_blank')
var img = document.createElement('img')
img.setAttribute('src', '/img/ferris/' + type.attr + '.svg')
img.setAttribute('title', type.title)
img.className = 'ferris'
a.appendChild(img)
element.parentElement.insertBefore(a, element)
}

@ -426,62 +426,62 @@
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/build-js.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/build-js.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/cache.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/cache.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/cargo-toml-lock.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/cargo-toml-lock.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/commands.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/commands.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/dependency.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/dependency.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/feature.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/feature.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/intro.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/intro.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/layout.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/layout.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/manifest.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/manifest.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/profile.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/profile.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/version.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/version.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>
<url>
<loc><![CDATA[http://course.rs/cargo/workspace.html]]></loc>
<loc><![CDATA[http://course.rs/toolchains/cargo/workspace.html]]></loc>
<lastmod>2021-12-30</lastmod>
<changefreq>weekly</changefreq>
</url>

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

@ -1,9 +0,0 @@
span.caption {
font-size: .8em;
font-weight: 600;
}
span.caption code {
font-size: 0.875em;
font-weight: 400;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@ -1,19 +1,19 @@
[book]
authors = ["sunface"]
language = "zh-CN"
title = "Rust语言圣经(Rust教程 Rust Course)"
title = "Rust语言圣经(Rust Course)"
src = "src"
[output.html]
additional-css = ["assets/ferris.css", "assets/theme/2018-edition.css"]
additional-js = ["assets/ferris.js"]
additional-css = ["theme/style3.css"]
additional-js = ["assets/custom.js", "assets/bigPicture.js"]
git-repository-url = "https://github.com/sunface/rust-course"
edit-url-template = "https://github.com/sunface/rust-course/edit/main/{path}"
[output.html.playground]
editable = true
copy-js = true
line-numbers = true
# line-numbers = true
[output.html.fold]
enable = true

@ -1,22 +1,27 @@
# Rust 语言圣经
[Rust语言圣经](about-book.md)
[进入 Rust 编程世界](into-rust.md)
[AWS 为何这么喜欢 Rust?](usecases/aws-rust.md)
[避免从入门到放弃](sth-you-should-not-do.md)
[关于本书](about-book.md)
[快速查询入口](index-list.md)
## Getting started
---
[StudyRust 社区](studyrust.md)
[一本生锈的书](rusty-book.md)
[Rust 语言周刊](rust-weekly.md)
[Rustt 翻译计划](rustt.md)
# 快速开始
---
- [寻找牛刀,以便小试](first-try/intro.md)
- [安装 Rust 环境](first-try/installation.md)
- [墙推 VSCode!](first-try/editor.md)
- [认识 Cargo](first-try/cargo.md)
- [不仅仅是 Hello world](first-try/hello-world.md)
## Rust 学习三部曲
- [下载依赖太慢了?](first-try/slowly-downloading.md)
- [避免从入门到放弃](first-try/sth-you-should-not-do.md)
# Rust语言特性
---
- [Rust 基础入门](basic/intro.md)
- [变量绑定与解构](basic/variable.md)
- [基本类型](basic/base-type/index.md)
@ -58,6 +63,7 @@
- [使用 use 引入模块及受限可见性](basic/crate-module/use.md)
- [注释和文档](basic/comment.md)
- [格式化输出](basic/formatted-output.md)
- [Rust 高级进阶](advance/intro.md)
- [生命周期](advance/lifetime/intro.md)
- [认识生命周期](advance/lifetime/basic.md)
@ -96,38 +102,47 @@
- [Macro 宏编程](advance/macro.md)
<!-- - [SIMD todo](advance/simd.md) -->
<!-- - [高阶特征约束(HRTB) todo](advance/hrtb.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)
## 专题内容,每个专题都配套一个小型项目进行实践
- [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)
# 常用工具链
---
- [自动化测试](test/intro.md)
- [编写测试及控制执行](test/write-tests.md)
- [单元测试和集成测试](test/unit-integration-test.md)
- [断言 assertion](test/assertion.md)
- [用 Github Actions 进行持续集成](test/ci.md)
- [用 GitHub Actions 进行持续集成](test/ci.md)
- [基准测试 benchmark](test/benchmark.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)
@ -151,9 +166,28 @@
- [通过 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)
- [构建脚本示例](cargo/reference/build-script/examples.md)
# 开发实践
---
- [企业落地实践](usecases/intro.md)
- [AWS 为何这么喜欢 Rust?](usecases/aws-rust.md)
- [手把手带你实现链表 doing](too-many-lists/intro.md)
- [日志和监控](logs/intro.md)
- [日志详解](logs/about-log.md)
- [日志门面 log](logs/log.md)
- [使用 tracing 记录日志](logs/tracing.md)
- [自定义 tracing 的输出格式](logs/tracing-logger.md)
- [监控](logs/observe/intro.md)
- [可观测性](logs/observe/about-observe.md)
- [分布式追踪](logs/observe/trace.md)
- [Rust 最佳实践](practice/intro.md)
- [日常开发三方库精选](practice/third-party-libs.md)
- [命名规范](practice/naming.md)
- [面试经验](practice/interview.md)
- [代码开发实践 todo](practice/best-pratice.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)
@ -162,49 +196,61 @@
- [还可以的单向链表](too-many-lists/ok-stack/intro.md)
- [优化类型定义](too-many-lists/ok-stack/type-optimizing.md)
- [定义 Peek 函数](too-many-lists/ok-stack/peek.md)
- [IntoIter 和 Iter](too-many-lists/ok-stack/iter.md)
- [IterMut以及完整代码](too-many-lists/ok-stack/itermut.md)
- [持久化单向链表](too-many-lists/persistent-stack/intro.md)
- [数据布局和基本操作](too-many-lists/persistent-stack/layout.md)
- [Drop、Arc 及完整代码](too-many-lists/persistent-stack/drop-arc.md)
- [不咋样的双端队列](too-many-lists/deque/intro.md)
- [数据布局和基本操作](too-many-lists/deque/layout.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)
- [栈借用](too-many-lists/unsafe-queue/stacked-borrow.md)
- [测试栈借用](too-many-lists/unsafe-queue/testing-stacked-borrow.md)
- [数据布局2](too-many-lists/unsafe-queue/layout2.md)
- [额外的操作](too-many-lists/unsafe-queue/extra-junk.md)
- [最终代码](too-many-lists/unsafe-queue/final-code.md)
- [使用高级技巧实现链表](too-many-lists/advanced-lists/intro.md)
- [生产级可用的双向链表](too-many-lists/advanced-lists/unsafe-deque.md)
- [双单向链表](too-many-lists/advanced-lists/double-singly.md)
- [栈上的链表](too-many-lists/advanced-lists/stack-allocated.md)
- [易混淆概念解析](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)
- [征服编译错误](compiler/intro.md)
- [对抗编译检查](compiler/fight-with-compiler/intro.md)
- [生命周期](compiler/fight-with-compiler/lifetime/intro.md)
- [生命周期过大-01](compiler/fight-with-compiler/lifetime/too-long1.md)
- [生命周期过大-02](compiler/fight-with-compiler/lifetime/too-long2.md)
- [循环中的生命周期](compiler/fight-with-compiler/lifetime/loop.md)
- [闭包碰到特征对象-01](compiler/fight-with-compiler/lifetime/closure-with-static.md)
- [重复借用](compiler/fight-with-compiler/borrowing/intro.md)
- [同时在函数内外使用引用](compiler/fight-with-compiler/borrowing/ref-exist-in-out-fn.md)
- [智能指针引起的重复借用错误](compiler/fight-with-compiler/borrowing/borrow-distinct-fields-of-struct.md)
- [类型未限制(todo)](compiler/fight-with-compiler/unconstrained.md)
- [幽灵数据(todo)](compiler/fight-with-compiler/phantom-data.md)
- [Rust 常见陷阱](compiler/pitfalls/index.md)
- [for 循环中使用外部数组](compiler/pitfalls/use-vec-in-for.md)
- [线程类型导致的栈溢出](compiler/pitfalls/stack-overflow.md)
- [算术溢出导致的 panic](compiler/pitfalls/arithmetic-overflow.md)
- [闭包中奇怪的生命周期](compiler/pitfalls/closure-with-lifetime.md)
- [可变变量不可变?](compiler/pitfalls/the-disabled-mutability.md)
- [可变借用失败引发的深入思考](compiler/pitfalls/multiple-mutable-references.md)
- [不太勤快的迭代器](compiler/pitfalls/lazy-iterators.md)
- [奇怪的序列 x..y](compiler/pitfalls/weird-ranges.md)
- [无处不在的迭代器](compiler/pitfalls/iterator-everywhere.md)
- [线程间传递消息导致主线程无法结束](compiler/pitfalls/main-with-channel-blocked.md)
- [警惕 UTF-8 引发的性能隐患](compiler/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)
- [Rust 性能优化 todo](profiling/intro.md)
- [深入内存 todo](profiling/memory/intro.md)
- [指针和引用 todo](profiling/memory/pointer-ref.md)
- [未初始化内存 todo](profiling/memory/uninit.md)
@ -236,28 +282,28 @@
- [HashMap todo](std/hashmap.md)
- [Iterator 常用方法 todo](std/iterator.md)
- [Ctrl-C/V: 编程常用代码片段 todo](cases/intro.md)
- [命令行解析 todo](cases/cmd.md)
- [配置文件解析 todo](cases/config.md)
- [编解码 todo](cases/encoding/intro.md)
- [JSON](cases/encoding/json.md)
- [CSV](cases/encoding/csv.md)
- [protobuf](cases/encoding/protobuf.md)
- [文件系统 todo](cases/file/intro.md)
- [文件读写](cases/file/file.md)
- [目录操作](cases/file/dir.md)
- [网络通信 todo](cases/protocol/intro.md)
- [HTTP](cases/protocol/http.md)
- [TCP](cases/protocol/tcp.md)
- [UDP](cases/protocol/udp.md)
- [gRPC](cases/protocol/grpc.md)
- [数据库访问 todo](cases/database.md)
- [正则表达式 todo](cases/regexp.md)
- [加密解密 todo](cases/crypto.md)
- [时间日期](cases/date.md)
- [开发调试 todo](cases/dev/intro.md)
- [日志](cases/dev/logs.md)
- [性能分析](cases/dev/profile.md)
<!-- - [配置文件解析 todo](cookbook/config.md)
- [编解码 todo](cookbook/encoding/intro.md)
- [JSON](cookbook/encoding/json.md)
- [CSV](cookbook/encoding/csv.md)
- [protobuf](cookbook/encoding/protobuf.md)
- [文件系统 todo](cookbook/file/intro.md)
- [文件读写](cookbook/file/file.md)
- [目录操作](cookbook/file/dir.md)
- [网络通信 todo](cookbook/protocol/intro.md)
- [HTTP](cookbook/protocol/http.md)
- [TCP](cookbook/protocol/tcp.md)
- [UDP](cookbook/protocol/udp.md)
- [gRPC](cookbook/protocol/grpc.md)
- [数据库访问 todo](cookbook/database.md)
- [正则表达式 todo](cookbook/regexp.md)
- [加密解密 todo](cookbook/crypto.md)
- [时间日期](cookbook/date.md)
- [开发调试 todo](cookbook/dev/intro.md)
- [日志](cookbook/dev/logs.md)
- [性能分析](cookbook/dev/profile.md) -->
<!--
- [Rust区块链入门]()
@ -265,15 +311,18 @@
- [Rust前端开发入门]()
- [Rust和WASM]() -->
## 附录
- [附录](appendix/intro.md)
- [A-关键字](appendix/keywords.md)
- [B-运算符与符号](appendix/operators.md)
- [C-表达式](appendix/expressions.md)
- [D-派生特征 trait](appendix/derive.md)
- [E-prelude 模块 todo](appendix/prelude.md)
- [F-Rust 版本说明](appendix/rust-version.md)
- [G-Rust 更新版本列表](appendix/rust-versions/intro.md)
# 附录
---
- [Appendix]()
- [关键字](appendix/keywords.md)
- [运算符与符号](appendix/operators.md)
- [表达式](appendix/expressions.md)
- [派生特征 trait](appendix/derive.md)
- [prelude 模块 todo](appendix/prelude.md)
- [Rust 版本说明](appendix/rust-version.md)
- [Rust 历次版本更新解读](appendix/rust-versions/intro.md)
- [1.58](appendix/rust-versions/1.58.md)
- [1.59](appendix/rust-versions/1.59.md)
- [1.60](appendix/rust-versions/1.60.md)

@ -1,58 +1,106 @@
# Rust 语言圣经 (The Course)
<img src="https://github.com/sunface/rust-course/blob/main/assets/banner.jpg?raw=true" />
- 在线阅读
- 官方: [https://course.rs](https://course.rs)
- 知乎: [支持章节内目录跳转,很好用!](https://www.zhihu.com/column/c_1452781034895446017)
Rust语言真的好连续六年成为全世界最受欢迎的语言、没有GC也无需手动内存管理、性能比肩 C++/C 还能直接调用它们的代码、安全性极高 - 总有公司说使用 Rust 后以前的大部分 bug 都将自动消失、全世界最好的包管理工具 Cargo 等等。但...
> 学习 Rust 光看书不够,精心设计的习题和项目实践可以让你事半功倍。[Rust By Practice](https://github.com/sunface/rust-by-practice) 是本书的配套习题和实践,覆盖了 easy to hard 各个难度,满足大家对 Rust 的所有期待。
>
> [Rust 语言周刊](https://github.com/sunface/rust-weekly),每周一发布,精选过去一周的技术文章、业界新闻、开源项目和 Rust 语言动态。
>
> Rust 优秀项目很多,如何在茫茫码海中与它们相遇?相比 Awesome Rust [Fancy Rust](https://github.com/sunface/fancy-rust) 能带给你全新的体验和选择。
### 教程简介
**`Rust语言圣经`**涵盖从**入门到精通**所需的 Rust 知识,目录及内容都经过深思熟虑的设计,同时语言生动幽默,行文流畅自如,摆脱技术书籍常有的机器味和晦涩感。
在 Rust 基础教学的同时,我们还提供了(部分):
- **深入度**,在基础教学的同时,提供了深入剖析。浅尝辄止并不能让我们站上紫禁之巅
- **性能优化**,选择 Rust意味着就要追求性能因此你需要体系化的了解性能优化
- **专题**,将 Rust 高级内容通过专题的方式一一呈现,内容内聚性极强
- **难点和错误索引**,作为一本工具书,优秀的索引能力非常重要,遗忘不可怕,找不到才可怕
- **场景化模版**,程序员上网查询如何操作文件是常事,没有人能记住所有代码,场景化模版可解君忧
总之在写作过程中我们始终铭记初心:为中国用户打造一门**全面的、深入的、持续更新的** Rust 教程。 新手用来入门,老手用来提高,高手用来提升生产力。
### 开源说明
在开源版权上,我们选择了 [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 语言圣经是**完全开源**的电子书,每个章节都至少用时 4-6 个小时才能初步完稿,牺牲了大量休闲娱乐、陪伴家人的时间,还没有任何钱赚
对于后面一句话我们持保留意见,如果以找工作为标准,那国内环境确实还不好,但如果你想成为更优秀的程序员或者是玩转开源,那 Rust 还真是不错的选择,具体原因见[下一章](https://course.rs/into-rust.html)。
**如果大家觉得这本书作者真的用心了,希望你能帮我们点一个 🌟 `star`。感激不尽!:)**
至于 Rust 难学,那正是本书要解决的问题,如果看完后,你觉得没有学会 Rust可以找我们退款哦抱歉这是开源书那就退 🌟 吧:)
### 借鉴的书籍
如果看到这里,大家觉得这本书的介绍并没有吸引到你,不要立即放弃,强烈建议读一下[进入 Rust 编程世界](https://course.rs/into-rust.html),那里会有不一样的精彩。
站在巨人的肩膀上,能帮我们看的更远,特此感谢以下巨人:
- [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 协议,并在这里统一对借鉴的书籍进行说明。
### 贡献者
非常感谢本教程的所有贡献者们,正是有了你们,才有了现在的高质量 Rust 教程!
- [@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)
尤其感谢这些主要贡献者,谢谢你们花费大量时间贡献了多处`fix`和高质量的内容优化。非常感动,再次感谢~~
> 本书完全开源,所有的文档内容都在 `GitHub` 上,至于里面还藏有什么秘密,大家点击右上角自行发现吧
>
> 小秘密一: 你们可能会好奇,这本书到底与其它 Rust 书籍有[哪些不同](https://github.com/sunface/rust-course#教程简介)
## 配套练习题
对于学习编程而言,读一篇文章不如做几道练习题,此话虽然夸张,但是也不无道理。既然如此,即读书又做练习题,效果会不会更好?再加上练习题是书本的配套呢? :P
- [Rust语言实战](https://github.com/sunface/rust-by-practice), Rust语言圣经配套习题支持中英双语可以在右上角切换
## 创作感悟
截至目前Rust语言圣经已写了 170 余章110 余万字,历经 800 多个小时,每一个章节都是手动写就,没有任何机翻和质量上的妥协( 相信深入阅读过的读者都能体会到这一点 )。
曾经有读者问过 "这么好的书为何要开源,而不是出版?",原因很简单:**只有完全开源才能完美地呈现出我想要的教学效果**。
总之Rust 要在国内真正发展起来,必须得有一些追逐梦想的人在做着不计付出的事情,而我希望自己能贡献一份微薄之力。
但是要说完全无欲无求,那也是不可能的,看到项目多了一颗 🌟,那感觉...棒极了,因为它代表了读者的认可和称赞。
你们用指尖绘制的星空,那里繁星点点,每一颗都在鼓励着怀揣着开源梦想的程序员披荆斩棘、不断前行,不夸张的说,没有你们,开源世界就没有星光,自然也就不会有今天的开源盛世。
因此,**我恳请大家,如果觉得书还可以,就在你的指尖星空绘制一颗新的 🌟,指引我们继续砥砺前行**。这个人世间,因善意而美好。
最后,能通过开源在茫茫人海中与大家相识,这感觉真好 :D
## 🏅 贡献者
非常感谢本教程的[所有贡献者](https://github.com/sunface/rust-course/graphs/contributors),正是有了你们,才有了现在的高质量 Rust 教程!
<br />
**🏆 贡献榜前三**(根据难易度、贡献次数、活跃度综合评定):
<table>
<tr>
<td align="center" width="25%">
<a href="https://github.com/sunface">
<img src="https://avatars.githubusercontent.com/u/7036754?v=4?s=100" alt=""/>
<br />
<sub><b>Sunface 🥇</b></sub>
</a>
</td>
<td align="center" width="25%">
<a href="https://github.com/AllanDowney">
<img src="https://avatars.githubusercontent.com/u/82752697?v=4?s=100" alt=""/>
<br />
<sub><b>AllanDowney 🥈</b></sub>
</a>
</td>
<td align="center" width="25%">
<a href="https://github.com/JesseAtSZ">
<img src="https://avatars.githubusercontent.com/u/35264598?v=4?s=100" alt=""/>
<br />
<sub><b>JesseAtSZ 🥉</b></sub>
</a>
</td>
</tr>
</table>
<br />
🏅 核心贡献者:
<table>
<tr>
<td align="center" width="25%">
<a href="https://github.com/1132719438">
<img src="https://avatars.githubusercontent.com/u/10138791?v=4?s=100" alt=""/>
<br />
<sub><b>1132719438</b></sub>
</a>
</td>
<td align="center" width="25%">
<a href="https://github.com/zongzi531">
<img src="https://avatars.githubusercontent.com/u/22429236?v=4?s=100" alt=""/>
<br />
<sub><b>zongzi531</b></sub>
</a>
</td>
<td align="center" width="25%">
<a href="https://github.com/Mintnoii">
<img src="https://avatars.githubusercontent.com/u/30466018?v=4?s=100" alt=""/>
<br />
<sub><b>Mintnoii</b></sub>
</a>
</td>
<td align="center" width="25%">
<a href="https://github.com/Rustln">
<img src="https://avatars.githubusercontent.com/u/100085326?v=4?s=100" alt=""/>
<br />
<sub><b>Rustln</b></sub>
</a>
</td>
</tr>
</table>

@ -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<Rc<RefCell<Node>>>`
## 总结

@ -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<String>,
@ -272,7 +272,7 @@ fn main() {
上面的代码也非常清晰,虽然使用了 `unsafe`,其实更多的是无奈之举,跟之前的 `unsafe` 实现完全不可同日而语。
其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的原生指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址!
其实 `Pin` 在这里并没有魔法,它也并不是实现自引用类型的主要原因,最关键的还是里面的指针的使用,而 `Pin` 起到的作用就是确保我们的值不会被移走,否则指针就会指向一个错误的地址!
## 使用 ouroboros

@ -4,7 +4,7 @@
并行和并发其实并不难,但是也给一些用户造成了困扰,因此我们专门开辟一个章节,用于讲清楚这两者的区别。
`Erlang` 之父 [`Joe Armstrong`](<https://en.wikipedia.org/wiki/Joe_Armstrong_(programmer)>)(伟大的异步编程先驱,开创一个时代的殿堂级计算机科学家,我还犹记得当年刚学到 `Erlang` 时的震撼respect用一张 5 岁小孩都能看的图片解释了并发与并行的区别:
`Erlang` 之父 [`Joe Armstrong`](<https://en.wikipedia.org/wiki/Joe_Armstrong_(programmer)>)(伟大的异步编程先驱,开创一个时代的殿堂级计算机科学家,我还犹记得当年刚学到 `Erlang` 时的震撼respect用一张 5 岁小孩都能看的图片解释了并发与并行的区别:
<img alt="" src="https://pic1.zhimg.com/80/f37dd89173715d0e21546ea171c8a915_1440w.png" class="center" />
@ -19,7 +19,7 @@
## CPU 多核
现在的个人计算机动辄拥有十来个核心M1 Max/Intel 12 代),如果使用串行的方式那真是太低了,因此我们把各种任务简单分成多个队列,每个队列都交给一个 CPU 核心去执行,当某个 CPU 核心没有任务时,它还能去其它核心的队列中偷任务(真·老黄牛),这样就实现了并行化处理。
现在的个人计算机动辄拥有十来个核心M1 Max/Intel 12 代),如果使用串行的方式那真是太低了,因此我们把各种任务简单分成多个队列,每个队列都交给一个 CPU 核心去执行,当某个 CPU 核心没有任务时,它还能去其它核心的队列中偷任务(真·老黄牛),这样就实现了并行化处理。
#### 单核心并发

@ -226,7 +226,7 @@ fn main() {
thread::sleep(Duration::from_secs(3));
println!("睡眠之后");
println!("收到值 {}", rx.recv().unwrap());
println!("receive {}", rx.recv().unwrap());
handle.join().unwrap();
}
```
@ -239,7 +239,7 @@ fn main() {
发送之后
//···睡眠3秒
睡眠之后
收到值 1
receive 1
```
主线程因为睡眠阻塞了 3 秒,因此并没有进行消息接收,而子线程却在此期间轻松完成了消息的发送。等主线程睡眠结束后,才姗姗来迟的从通道中接收了子线程老早之前发送的消息。
@ -279,11 +279,11 @@ fn main() {
发送之前
//···睡眠3秒
睡眠之后
收到值 1
receive 1
发送之后
```
可以看出,主线程由于睡眠被阻塞导致无法接收消息,因此子线程的发送也一直被阻塞,直到主线程结束睡眠并成功接收消息后,发送才成功:**发送之后**的输出是在**收到值 1**之后,说明**只有接收消息彻底成功后,发送消息才算完成**。
可以看出,主线程由于睡眠被阻塞导致无法接收消息,因此子线程的发送也一直被阻塞,直到主线程结束睡眠并成功接收消息后,发送才成功:**发送之后**的输出是在**receive 1**之后,说明**只有接收消息彻底成功后,发送消息才算完成**。
#### 消息缓存

@ -1,6 +1,6 @@
# 基于 Send 和 Sync 的线程安全
为何 Rc、RefCell 和原生指针不可以在多线程间使用?如何让原生指针可以在多线程使用?我们一起来探寻下这些问题的答案。
为何 Rc、RefCell 和裸指针不可以在多线程间使用?如何让裸指针可以在多线程使用?我们一起来探寻下这些问题的答案。
## 无法用于多线程的`Rc`
@ -27,7 +27,7 @@ error[E0277]: `Rc<i32>` cannot be sent between threads safely
= help: within `[closure@src/main.rs:5:27: 7:6]`, the trait `Send` is not implemented for `Rc<i32>`
```
表面原因是`Rc`无法在线程间安全的转移,实际是编译器给予我们的那句帮助: `the trait Send is not implemented for Rc<i32>`(`Rc<i32>`未实现`Send`特征), 那么此处的`Send`特征又是何方神圣?
表面原因是`Rc`无法在线程间安全的转移,实际是编译器给予我们的那句帮助: ```the trait `Send` is not implemented for `Rc<i32>` ```(`Rc<i32>`未实现`Send`特征), 那么此处的`Send`特征又是何方神圣?
## Rc 和 Arc 源码对比
@ -50,7 +50,7 @@ unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
`Send`和`Sync`是 Rust 安全并发的重中之重,但是实际上它们只是标记特征(marker trait该特征未定义任何行为因此非常适合用于标记), 来看看它们的作用:
- 实现`Send`的类型可以在线程间安全的传递其所有权
- 实现`Sync`的类型可以在线程间安全的共享(通过引用)
- 实现`Sync`的类型可以在线程间安全的共享(通过引用)
这里还有一个潜在的依赖:一个类型要在线程间安全的共享的前提是,指向它的引用必须能在线程间传递。因为如果引用都不能被传递,我们就无法在多个线程间使用引用去访问同一个数据了。
@ -62,7 +62,7 @@ unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLock<T> {}
```
首先`RwLock`可以在线程间安全的共享,那它肯定是实现了`Sync`,但是我们的关注点不在这里。众周知,`RwLock`可以并发的读,说明其中的值`T`必定也可以在线程间共享,那`T`必定要实现`Sync`。
首先`RwLock`可以在线程间安全的共享,那它肯定是实现了`Sync`,但是我们的关注点不在这里。众周知,`RwLock`可以并发的读,说明其中的值`T`必定也可以在线程间共享,那`T`必定要实现`Sync`。
果不其然,上述代码中,`T`的特征约束中就有一个`Sync`特征,那问题又来了,`Mutex`是不是相反?再来看看:
@ -80,19 +80,19 @@ unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
正是因为以上规则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`类型](https://course.rs/advance/into-types/custom-type.html#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`,例如文中的指针例子

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

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

@ -263,7 +263,7 @@ for handle in handles {
}
```
按理来说,既然是无锁实现了,那么锁的开销应该几乎没有,性能会随着线程数的增加几近线程增长,但是真的是这样吗?
按理来说,既然是无锁实现了,那么锁的开销应该几乎没有,性能会随着线程数的增加接近线性增长,但是真的是这样吗?
下图是该代码在 `48` 核机器上的运行结果:
@ -475,10 +475,10 @@ fn main() {
```rust
use std::thread;
use std::sync::{Once, ONCE_INIT};
use std::sync::Once;
static mut VAL: usize = 0;
static INIT: Once = ONCE_INIT;
static INIT: Once = Once::new();
fn main() {
let handle1 = thread::spawn(move || {
@ -516,7 +516,7 @@ fn main() {
## 总结
[Rust 的线程模型](./intro.md)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
[Rust 的线程模型](https://course.rs/advance/concurrency-with-threads/intro.html)是 `1:1` 模型,因为 Rust 要保持尽量小的运行时。
我们可以使用 `thread::spawn` 来创建线程,创建出的多个线程之间并不存在执行顺序关系,因此代码逻辑千万不要依赖于线程间的执行顺序。

@ -0,0 +1 @@
# 裸指针、引用和智能指针 todo

@ -24,7 +24,7 @@ error[E0277]: the size for values of type `str` cannot be known at compilation t
| ^^^^^^ doesn't have a size known at compile-time
```
编译器准确的告诉了我们原因:`str` 字符串切片它是 [`DST` 动态大小类型](https://course.rs/advance/custom-type.html#动态大小类型),这意味着编译器无法在编译期知道 `str` 类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
编译器准确的告诉了我们原因:`str` 字符串切片它是 [`DST` 动态大小类型](https://course.rs/advance/into-types/sized.html#动态大小类型-dst),这意味着编译器无法在编译期知道 `str` 类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
也就是说,我们无法直接使用 `str`,而对于 `[u8]` 也是类似的,大家可以自己动手试试。
@ -69,7 +69,7 @@ let s3: &[i32] = &arr[1..3];
我们常常说使用切片,实际上我们在用的是切片的引用,我们也在频繁说使用字符串,实际上我们在使用的也是字符串切片的引用。
总之,切片在 Rust 中是动态类型 DST是无法被我们直接使用的而我们在使用的都是切片的引用。
总之,切片在 Rust 中是动态大小类型 DST是无法被我们直接使用的而我们在使用的都是切片的引用。
| 切片 | 切片引用 |
| -------------- | --------------------- |

@ -8,7 +8,7 @@ Rust 语言的类型可以大致分为两种:基本类型和标准库类型,
## str
如上所述,`str` 是唯一定义在 Rust 语言特性中的字符串,但是也是我们几乎不会用到的字符串类型,为何?
原因在于 `str` 字符串它是 [`DST` 动态大小类型](https://course.rs/advance/custom-type.html#动态大小类型),这意味着编译器无法在编译期知道 `str` 类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
原因在于 `str` 字符串它是 [`DST` 动态大小类型](https://course.rs/advance/into-types/sized.html#动态大小类型-dst),这意味着编译器无法在编译期知道 `str` 类型的大小,只有到了运行期才能动态获知,这对于强类型、强安全的 Rust 语言来说是不可接受的。
```rust
let string: str = "banana";
@ -28,7 +28,7 @@ error[E0277]: the size for values of type `str` cannot be known at compilation t
同时还是 String 和 &str 的底层数据类型。 由于 str 是动态
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF8 编码**。
`str` 类型是硬编码进可执行文件,也无法被修改,但是 `String` 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,**当 Rust 用户提到字符串时,往往指的就是 `String` 类型和 `&str` 字符串切片类型,这两个类型都是 UTF-8 编码**。
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` 和` CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。

@ -513,7 +513,7 @@ fn render() -> Result<String, std::io::Error> {
上面的代码会报错,原因在于 `render` 函数中的两个 `?` 返回的实际上是不同的错误:`env::var()` 返回的是 `std::env::VarError`,而 `read_to_string` 返回的是 `std::io::Error`
为了满足 `render` 函数的签名,我们就需要将 `env::VarError``io::Error` 归一化为同一种错误类型。要实现这个目的有种方式:
为了满足 `render` 函数的签名,我们就需要将 `env::VarError``io::Error` 归一化为同一种错误类型。要实现这个目的有种方式:
- 使用特征对象 `Box<dyn Error>`
- 自定义错误类型

@ -220,7 +220,7 @@ let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
```
可以看出第一行的函数和后面的闭包其实在形式上是非常接近的,同时三种不同的闭包也展示了三种不同的使用方式:省略参数、返回值和花括号对。
可以看出第一行的函数和后面的闭包其实在形式上是非常接近的,同时三种不同的闭包也展示了三种不同的使用方式:省略参数、返回值类型和花括号对。
虽然类型推导很好用,但是它不是泛型,**当编译器推导出一种类型后,它就会一直使用该类型**
@ -303,7 +303,7 @@ where
}
```
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `String` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](http://exercise.rs/functional-programming/closure.html)
上面的缓存有一个很大的问题:只支持 `u32` 类型的值,若我们想要缓存 `String` 类型,显然就行不通了,因此需要将 `u32` 替换成泛型 `E`,该练习就留给读者自己完成,具体代码可以参考[这里](https://practice.rs/functional-programing/cloure.html#closure-in-structs)
## 捕获作用域中的值
@ -421,6 +421,15 @@ false
如果你想强制闭包取得捕获变量的所有权,可以在参数列表前添加 `move` 关键字,这种用法通常用于闭包的生命周期大于捕获变量的生命周期时,例如将闭包返回或移入其他线程。
```rust
use std::thread;
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {
println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
```
2. `FnMut`,它以可变借用的方式捕获了环境中的值,因此可以修改该值:
```rust
@ -699,7 +708,7 @@ help: use `impl Fn(i32) -> i32` as the return type, as all return paths are of t
嗯,编译器提示我们加一个 `impl` 关键字,哦,这样一说,读者可能就想起来了,`impl Trait` 可以用来返回一个实现了指定特征的类型,那么这里 `impl Fn(i32) -> i32` 的返回值形式,说明我们要返回一个闭包类型,它实现了 `Fn(i32) -> i32` 特征。
完美解决,但是,在[特征](../../basic/trait/trait.md)那一章,我们提到过,`impl Trait` 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:
完美解决,但是,在[特征](https://course.rs/basic/trait/trait.html)那一章,我们提到过,`impl Trait` 的返回方式有一个非常大的局限,就是你只能返回同样的类型,例如:
```rust
fn factory(x:i32) -> impl Fn(i32) -> i32 {
@ -758,3 +767,6 @@ fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> {
## 闭包的生命周期
这块儿内容在进阶生命周期章节中有讲,这里就不再赘述,读者可移步[此处](https://course.rs/advance/lifetime/advance.html#闭包函数的消除规则)进行回顾。
{{#include ../../practice.md}}

@ -71,7 +71,7 @@ fn main() {
}
```
关于原子类型的讲解看[这篇文章](./concurrency-with-threads/sync2.md)
关于原子类型的讲解看[这篇文章](https://course.rs/advance/concurrency-with-threads/sync2.html)
#### 示例:全局 ID 生成器
@ -115,10 +115,10 @@ impl Factory{
```rust
use std::sync::Mutex;
static names: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen"));
static NAMES: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen"));
fn main() {
let v = names.lock().unwrap();
let v = NAMES.lock().unwrap();
println!("{}",v);
}
```
@ -129,10 +129,10 @@ fn main() {
error[E0015]: calls in statics are limited to constant functions, tuple structs and tuple variants
--> src/main.rs:3:42
|
3 | static names: Mutex<String> = Mutex::new(String::from("sunface"));
3 | static NAMES: Mutex<String> = Mutex::new(String::from("sunface"));
```
但你又必须在声明时就对`names`进行初始化,此时就陷入了两难的境地。好在天无绝人之路,我们可以使用`lazy_static`包来解决这个问题。
但你又必须在声明时就对`NAMES`进行初始化,此时就陷入了两难的境地。好在天无绝人之路,我们可以使用`lazy_static`包来解决这个问题。
#### lazy_static
@ -142,11 +142,11 @@ error[E0015]: calls in statics are limited to constant functions, tuple structs
use std::sync::Mutex;
use lazy_static::lazy_static;
lazy_static! {
static ref names: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen"));
static ref NAMES: Mutex<String> = Mutex::new(String::from("Sunface, Jack, Allen"));
}
fn main() {
let mut v = names.lock().unwrap();
let mut v = NAMES.lock().unwrap();
v.push_str(", Myth");
println!("{}",v);
}
@ -195,27 +195,27 @@ struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
static mut CONFIG: Option<&mut Config> = None;
fn main() {
unsafe {
config = Some(&mut Config {
CONFIG = Some(&mut Config {
a: "A".to_string(),
b: "B".to_string(),
});
println!("{:?}",config)
println!("{:?}", CONFIG)
}
}
```
以上代码我们声明了一个全局动态配置`config`,并且其值初始化为`None`,然后在程序开始运行后,给它赋予相应的值,运行后报错:
以上代码我们声明了一个全局动态配置`CONFIG`,并且其值初始化为`None`,然后在程序开始运行后,给它赋予相应的值,运行后报错:
```console
error[E0716]: temporary value dropped while borrowed
--> src/main.rs:10:28
|
10 | config = Some(&mut Config {
10 | CONFIG = Some(&mut Config {
| _________-__________________^
| |_________|
| ||
@ -228,19 +228,17 @@ error[E0716]: temporary value dropped while borrowed
| creates a temporary which is freed while still in use
```
可以看到Rust 的借用和生命周期规则限制了我们做到这一点,因为试图将一个局部生命周期的变量赋值给全局生命周期的`config`,这明显是不安全的。
可以看到Rust 的借用和生命周期规则限制了我们做到这一点,因为试图将一个局部生命周期的变量赋值给全局生命周期的`CONFIG`,这明显是不安全的。
好在`Rust`为我们提供了`Box::leak`方法,它可以将一个变量从内存中泄漏(听上去怪怪的,竟然做主动内存泄漏),然后将其变为`'static`生命周期,最终该变量将和程序活得一样久,因此可以赋值给全局静态变量`config`。
好在`Rust`为我们提供了`Box::leak`方法,它可以将一个变量从内存中泄漏(听上去怪怪的,竟然做主动内存泄漏),然后将其变为`'static`生命周期,最终该变量将和程序活得一样久,因此可以赋值给全局静态变量`CONFIG`。
```rust
use std::sync::Mutex;
#[derive(Debug)]
struct Config {
a: String,
b: String
}
static mut config: Option<&mut Config> = None;
static mut CONFIG: Option<&mut Config> = None;
fn main() {
let c = Box::new(Config {
@ -250,8 +248,8 @@ fn main() {
unsafe {
// 将`c`从内存中泄漏,变成`'static`生命周期
config = Some(Box::leak(c));
println!("{:?}", config);
CONFIG = Some(Box::leak(c));
println!("{:?}", CONFIG);
}
}
```
@ -266,7 +264,7 @@ struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
static mut CONFIG: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
Some(&mut Config {
@ -278,9 +276,9 @@ fn init() -> Option<&'static mut Config> {
fn main() {
unsafe {
config = init();
CONFIG = init();
println!("{:?}",config)
println!("{:?}", CONFIG)
}
}
```
@ -293,7 +291,7 @@ struct Config {
a: String,
b: String,
}
static mut config: Option<&mut Config> = None;
static mut CONFIG: Option<&mut Config> = None;
fn init() -> Option<&'static mut Config> {
let c = Box::new(Config {
@ -307,16 +305,71 @@ fn init() -> Option<&'static mut Config> {
fn main() {
unsafe {
config = init();
CONFIG = init();
println!("{:?}",config)
println!("{:?}", CONFIG)
}
}
```
## 标准库中的 OnceCell
@todo
`Rust` 标准库中提供 `lazy::OnceCell``lazy::SyncOnceCell` 两种 `Cell`,前者用于单线程,后者用于多线程,它们用来存储堆上的信息,并且具有最多只能赋值一次的特性。 如实现一个多线程的日志组件 `Logger`
```rust
#![feature(once_cell)]
use std::{lazy::SyncOnceCell, thread};
fn main() {
// 子线程中调用
let handle = thread::spawn(|| {
let logger = Logger::global();
logger.log("thread message".to_string());
});
// 主线程调用
let logger = Logger::global();
logger.log("some message".to_string());
let logger2 = Logger::global();
logger2.log("other message".to_string());
handle.join().unwrap();
}
#[derive(Debug)]
struct Logger;
static LOGGER: SyncOnceCell<Logger> = SyncOnceCell::new();
impl Logger {
fn global() -> &'static Logger {
// 获取或初始化 Logger
LOGGER.get_or_init(|| {
println!("Logger is being created..."); // 初始化打印
Logger
})
}
fn log(&self, message: String) {
println!("{}", message)
}
}
```
以上代码我们声明了一个 `global()` 关联函数,并在其内部调用 `get_or_init` 进行初始化 `Logger`,之后在不同线程上多次调用 `Logger::global()` 获取其实例:
```console
Logger is being created...
some message
other message
thread message
```
可以看到,`Logger is being created...` 在多个线程中使用也只被打印了一次。
特别注意,目前 `OnceCell``SyncOnceCell` API 暂未稳定,需启用特性 `#![feature(once_cell)]`
## 总结

@ -110,7 +110,7 @@ type Meters = u32
**类型别名并不是一个独立的全新的类型,而是某一个类型的别名**,因此编译器依然会把 `Meters``u32` 来使用:
```rust
type Meters = i32;
type Meters = u32;
let x: u32 = 5;
let y: Meters = 5;

@ -139,6 +139,7 @@ fn main() {
但是上面的代码有个问题,你需要为每个枚举成员都实现一个转换分支,非常麻烦。好在可以使用宏来简化,自动根据枚举的定义来实现`TryFrom`特征:
```rust
#[macro_export]
macro_rules! back_to_enum {
($(#[$meta:meta])* $vis:vis enum $name:ident {
$($(#[$vmeta:meta])* $vname:ident $(= $val:expr)?,)*
@ -174,7 +175,7 @@ back_to_enum! {
**这个方法原则上并不推荐,但是有其存在的意义,如果要使用,你需要清晰的知道自己为什么使用**。
在之前的类型转换章节,我们提到过非常邪恶的[`transmute`转换](<../basic/converse.md#(Transmutes)>),其实,当你知道数值一定不会超过枚举的范围时(例如枚举成员对应 123传入的整数也在这个范围内),就可以使用这个方法完成变形。
在之前的类型转换章节,我们提到过非常邪恶的[`transmute`转换](https://course.rs/basic/converse.html#变形记transmutes),其实,当你知道数值一定不会超过枚举的范围时(例如枚举成员对应 123传入的整数也在这个范围内),就可以使用这个方法完成变形。
> 最好使用#[repr(..)]来控制底层类型的大小,免得本来需要 i32结果传入 i64最终内存无法对齐产生奇怪的结果

@ -15,7 +15,7 @@
**正因为编译器无法在编译期获知类型大小,若你试图在代码中直接使用 DST 类型,将无法通过编译。**
现在给你一个挑战:想出几个 DST 类型。俺厚黑地说一句,估计大部分人都想不出这样的一个类型,就连我,如果不是查询着资料在写,估计一时半会儿想不到一个。
现在给你一个挑战:想出几个 DST 类型。俺厚黑地说一句,估计大部分人都想不出这样的一个类型,就连我,如果不是查询着资料在写,估计一时半会儿想不到一个。
先来看一个最直白的:

@ -77,6 +77,8 @@ fn main() {
这就解释了可变借用为啥会在 `main` 函数作用域内有效,最终导致 `foo.share()` 无法再进行不可变借用。
总结下:`&mut self` 借用的生命周期和 `loan` 的生命周期相同,将持续到 `println` 结束。而在此期间 `foo.share()` 又进行了一次不可变 `&foo` 借用,违背了可变借用与不可变借用不能同时存在的规则,最终导致了编译错误。
上述代码实际上完全是正确的,但是因为生命周期系统的“粗糙实现”,导致了编译错误,目前来说,遇到这种生命周期系统不够聪明导致的编译错误,我们也没有太好的办法,只能修改代码去满足它的需求,并期待以后它会更聪明。
#### 例子 2
@ -136,7 +138,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 {
@ -254,7 +256,7 @@ let closure_slision = |x: &i32| -> &i32 { x };
## NLL (Non-Lexical Lifetime)
之前我们在[引用与借用](../../basic/ownership/borrowing.md#NLL)那一章其实有讲到过这个概念,简单来说就是:**引用的生命周期正常来说应该从借用开始一直持续到作用域结束**,但是这种规则会让多引用共存的情况变得更复杂:
之前我们在[引用与借用](https://course.rs/basic/ownership/borrowing.html#NLL)那一章其实有讲到过这个概念,简单来说就是:**引用的生命周期正常来说应该从借用开始一直持续到作用域结束**,但是这种规则会让多引用共存的情况变得更复杂:
```rust
fn main() {

@ -44,7 +44,7 @@ error[E0597]: `x` does not live long enough // `x` 活得不够久
| - borrow later used here // 对 `x` 的借用在此处被使用
```
在这里 `r` 拥有更大的作用域,或者说**活得更久**。如果 Rust 不阻止该悬引用的发生,那么当 `x` 被释放后,`r` 所引用的值就不再是合法的,会导致我们程序发生异常行为,且该异常行为有时候会很难被发现。
在这里 `r` 拥有更大的作用域,或者说**活得更久**。如果 Rust 不阻止该悬引用的发生,那么当 `x` 被释放后,`r` 所引用的值就不再是合法的,会导致我们程序发生异常行为,且该异常行为有时候会很难被发现。
## 借用检查
@ -578,7 +578,19 @@ Bang一个复杂的玩意儿被甩到了你面前就问怕不怕
就关键点稍微解释下:
- `'a: 'b`,是生命周期约束语法,跟泛型约束非常相似,用于说明 `'a` 必须比 `'b` 活得久
- 为了实现这一点,必须把 `'a``'b` 都在同一个地方声明,你不能把 `'a``impl` 后面声明,而把 `'b` 在方法中声明
- 可以把 `'a``'b` 都在同一个地方声明(如上),或者分开声明但通过 `where 'a: 'b` 约束生命周期关系,如下:
```rust
impl<'a> ImportantExcerpt<'a> {
fn announce_and_return_part<'b>(&'a self, announcement: &'b str) -> &'b str
where
'a: 'b,
{
println!("Attention please: {}", announcement);
self.part
}
}
```
总之,实现方法比想象中简单:加一个约束,就能暗示编译器,尽管引用吧,反正我想引用的内容比我活得久,爱咋咋地,我怎么都不会引用到无效的内容!

@ -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);
}
```
@ -80,7 +80,51 @@ fn main() {
## `T: 'static`
相比起来,我们的生命周期约束就弱得多了,它只能试图向编译器表达:如果可以的话,我想要一个可以一直存活的变量, see ? 跟 `&'static` 表达的强度完全不一样,下面用例子来说明:
相比起来,这种形式的约束就有些复杂了。
首先,在以下两种情况下,`T: 'static` 与 `&'static` 有相同的约束:`T` 必须活得和程序一样久。
```rust
use std::fmt::Debug;
fn print_it<T: Debug + 'static>( input: T) {
println!( "'static value passed in is: {:?}", input );
}
fn print_it1( input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}
fn main() {
let i = 5;
print_it(&i);
print_it1(&i);
}
```
以上代码会报错,原因很简单: `&i` 的生命周期无法满足 `'static` 的约束,如果大家将 `i` 修改为常量,那自然一切 OK。
见证奇迹的时候,请不要眨眼,现在我们来稍微修改下 `print_it` 函数:
```rust
use std::fmt::Debug;
fn print_it<T: Debug + 'static>( input: &T) {
println!( "'static value passed in is: {:?}", input );
}
fn main() {
let i = 5;
print_it(&i);
}
```
这段代码竟然不报错了!原因在于我们约束的是 `T`,但是使用的却是它的引用 `&T`,换而言之,我们根本没有直接使用 `T`,因此编译器就没有去检查 `T` 的生命周期约束!它只要确保 `&T` 的生命周期符合规则即可,在上面代码中,它自然是符合的。
再来看一个例子:
```rust
use std::fmt::Display;
@ -121,49 +165,29 @@ fn static_bound<T: Display + 'static>(t: &T) {
}
```
以上代码充分说明了两个问题:
- `'static` 生命周期的数据可以一直存活,因此 `r1``r2` 才能在语句块内部被赋值
- `T: 'static` 的约束真的很弱,`s1` 明明生命周期只在内部语句块内有效,但是该约束依然可以满足,`static_bound` 成功被调用
## 两者的区别
总之, `&'static` != `T: 'static` ,虽然它们看起来真的非常像。
为了进一步验证,我们修改下 `static_bound` 的签名 :
## static 到底针对谁?
大家有没有想过,到底是 `&'static` 这个引用还是该引用指向的数据活得跟程序一样久呢?
**答案是引用指向的数据**,而引用本身是要遵循其作用域范围的,我们来简单验证下:
```rust
use std::fmt::Display;
fn main() {
let s1 = "String".to_string();
{
let static_string = "I'm in read-only memory";
println!("static_string: {}", static_string);
static_bound(&s1);
}
// 当 `static_string` 超出作用域时,该引用不能再被使用,但是数据依然会存在于 binary 所占用的内存中
}
fn static_bound<T: Display>(t: &'static T) {
println!("{}", t);
println!("static_string reference remains alive: {}", static_string);
}
```
在这里,不再使用生命周期约束来限制 `T`,而直接指定 `T` 的生命周期是 `&'static` ,不出所料,代码报错了:
```console
error[E0597]: `s1` does not live long enough
--> src/main.rs:8:18
|
8 | static_bound(&s1);
| -------------^^^-
| | |
| | borrowed value does not live long enough
| argument requires that `s1` is borrowed for `'static`
9 | }
| - `s1` dropped here while still borrowed
```
以上代码不出所料会报错,原因在于虽然字符串字面量 "I'm in read-only memory" 的生命周期是 `'static`,但是持有它的引用并不是,它的作用域在内部花括号 `}` 处就结束了。
原因很简单,`s1` 活得不够久,没有满足 `'static` 的生命周期要求。
## 使用经验
## 总结
总之, `&'static``T: 'static` 大体上相似,相比起来,后者的使用形式会更加复杂一些。
至此,相信大家对于 `'static``T: 'static` 也有了清晰的理解,那么我们应该如何使用它们呢?
@ -173,3 +197,4 @@ error[E0597]: `s1` does not live long enough
- 如果你希望满足和取悦编译器,那就使用 `T: 'static`,很多时候它都能解决问题
> 一个小知识,在 Rust 标准库中,有 48 处用到了 &'static 112 处用到了 `T: 'static` ,看来取悦编译器不仅仅是菜鸟需要的,高手也经常用到 :)

@ -311,7 +311,7 @@ hello_macro
由于过程宏所在的包跟我们的项目紧密相连,因此将它放在项目之中。现在,问题又来了,该如何在项目的 `src/main.rs` 中引用 `hello_macro_derive` 包的内容?
方法有两种,第一种是将 `hello_macro_derive` 发布到 `crates.io``github` 中,就像我们引用的其它依赖一样;另一种就是使用相对路径引入的本地化方式,修改 `hello_macro/Cargo.toml` 文件添加以下内容:
方法有两种,第一种是将 `hello_macro_derive` 发布到 `crates.io``GitHub` 中,就像我们引用的其它依赖一样;另一种就是使用相对路径引入的本地化方式,修改 `hello_macro/Cargo.toml` 文件添加以下内容:
```toml
[dependencies]
@ -492,6 +492,7 @@ let sql = sql!(SELECT * FROM posts WHERE id=1);
3. [syn](https://crates.io/crates/syn) 和 [quote](https://crates.io/crates/quote) ,用于编写过程宏的包,它们的文档有很多值得学习的东西
4. [Structuring, testing and debugging procedural macro crates](https://www.reddit.com/r/rust/comments/rjumsg/any_good_resources_for_learning_rust_macros/)从测试、debug、结构化的角度来编写过程宏
5. [blog.turbo.fish](https://blog.turbo.fish),里面的过程宏系列文章值得一读
6. [Rust 宏小册中文版](https://zjp-cn.github.io/tlborm/),非常详细的解释了宏各种知识
## 总结

@ -264,7 +264,7 @@ fn gen_static_str() -> &'static str{
光看上面的描述,大家可能还是云里雾里、一头雾水。
那么我说一个简单的场景,**你需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久**,那么可以使用 `Box::leak`,例如有一个存储配置的结构体实例,它是在运行期动态插入内容,那么就可以将其转为全局有效,虽然 `Rc/Arc` 也可以实现此功能,但是 `Box::leak` 是性能最高的。
那么我说一个简单的场景,**你需要一个在运行期初始化的值,但是可以全局有效,也就是和整个程序活得一样久**,那么可以使用 `Box::leak`,例如有一个存储配置的结构体实例,它是在运行期动态插入内容,那么就可以将其转为全局有效,虽然 `Rc/Arc` 也可以实现此功能,但是 `Box::leak` 是性能最高的。
## 总结

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

@ -52,7 +52,7 @@ fn main() {
不要被 `clone` 字样所迷惑,以为所有的 `clone` 都是深拷贝。这里的 `clone` **仅仅复制了智能指针并增加了引用计数,并没有克隆底层数据**,因此 `a``b` 是共享了底层的字符串 `s`,这种**复制效率是非常高**的。当然你也可以使用 `a.clone()` 的方式来克隆,但是从可读性角度,我们更加推荐 `Rc::clone` 的方式。
实际上在 Rust 中,还有不少 `clone` 都是浅拷贝,例如[迭代器的克隆](https://course.rs/pitfalls/iterator-everywhere.html)。
实际上在 Rust 中,还有不少 `clone` 都是浅拷贝,例如[迭代器的克隆](https://course.rs/compiler/pitfalls/iterator-everywhere.html)。
#### 观察引用计数的变化

@ -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 种能力时,编译器才不会进行内存安全方面的检查,最典型的就是使用**裸指针**(引用和裸指针有很大的区别)。
## 谈虎色变?

@ -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<i32> = 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` 来告诉编译器,它无需操心,剩下的交给我们自己来处理。
@ -407,7 +407,7 @@ extern "C" {
- 数据竞争
- 内存对齐问题
但是需要注意的是,它只能帮助识别被执行代码路径的风险,些未被执行到的代码是没办法被识别的。
但是需要注意的是,它只能帮助识别被执行代码路径的风险,些未被执行到的代码是没办法被识别的。
#### Clippy
@ -427,7 +427,7 @@ extern "C" {
## 总结
至此,`unsafe` 的五种兵器已介绍完毕,大家是否意犹未尽?我想说的是,就算意犹未尽,也没有其它器了。
至此,`unsafe` 的五种兵器已介绍完毕,大家是否意犹未尽?我想说的是,就算意犹未尽,也没有其它器了。
就像上一章中所提到的,`unsafe` 只应该用于这五种场景,其它场景,你应该坚决的使用安全的代码,否则就会像 `actix-web` 的前作者一样,被很多人议论,甚至被喷。。。

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

@ -26,7 +26,7 @@
- `match` - 模式匹配
- `mod` - 定义一个模块
- `move` - 使闭包获取其所捕获项的所有权
- `mut` - 在引用、原生指针或模式绑定中使用,表明变量是可变的
- `mut` - 在引用、指针或模式绑定中使用,表明变量是可变的
- `pub` - 表示结构体字段、`impl` 块或模块的公共可见性
- `ref` - 通过引用绑定
- `return` - 从函数中返回

@ -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` |

@ -0,0 +1,114 @@
# Rust 新版解读 | 1.60 | 重点: 查看 Cargo 构建耗时详情、Cargo Feature 增加新语法
> 原文链接: https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html
通过 [rustup](https://www.rust-lang.org/tools/install) 安装的同学可以使用以下命令升级到 1.60 版本:
```shell
$ rustup update stable
```
## 基于源码的代码覆盖
rustc 新增了基于 LLVM 的代码覆盖率测量,想要测试的同学可以通过以下方式重新构建你的项目:
```shell
RUSTFLAGS="-C instrument-coverage" cargo build
```
运行新生成的可执行文件将在当前目录下产生一个 `default.profraw` 文件( 路径和文件名可以通过环境变量进行[覆盖](https://doc.rust-lang.org/stable/rustc/instrument-coverage.html#running-the-instrumented-binary-to-generate-raw-coverage-profiling-data) )。
`llvm-tools-preview` 组件包含了 `llvm-profdata`,可以用于处理和合并<ruby>原生的测量结果输出<rt>raw profile output)</rt></ruby>(测量区域执行数)。
`llvm-cov` 用于报告生成,它将 `llvm-profdata` 处理后的输出跟二进制可执行文件自身相结合,对于前者大家可能好理解,但是为何要跟后者可执行文件相结合呢?原因在于可执行文件中嵌入了一个从计数器到实际源代码单元的映射。
```shell
rustup component add llvm-tools-preview
$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-profdata merge -sparse default.profraw -o default.profdata
$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/llvm-cov show -Xdemangler=rustfilt target/debug/coverage-testing \
-instr-profile=default.profdata \
-show-line-counts-or-regions \
-show-instantiations
```
基于一个简单的 hello world 可执行文件,执行以上命令就可以获得如下带有标记的结果:
```rust
1| 1|fn main() {
2| 1| println!("Hello, world!");
3| 1|}
```
从结果中可以看出:每一行代码都已经被成功覆盖。
如果大家还想要了解更多,可以看下[官方的 rustc 文档](https://doc.rust-lang.org/rustc/instrument-coverage.html)。目前来说,基准功能已经稳定了,并将以某种形式存在于未来所有的 Rust 发布版本中。 但输出格式和产生这些输出的 LLVM 工具可能依然会发生变化,基于此,大家在使用时需要确保 `llvm-tools-preview` 和 rustc ( 用于编译代码的 )使用了相同的版本。
## 查看 Cargo 构建耗时
新版本中,以下命令已经可以正常使用了:
```shell
$ cargo build --timings
Compiling hello-world v0.1.0 (hello-world)
Timing report saved to target/cargo-timings/cargo-timing-20220318T174818Z.html
Finished dev [unoptimized + debuginfo] target(s) in 0.98s
```
此命令会生成一个 `cargo build` 的耗时详情报告,除了上面提到的路径外,报告还会被拷贝到 `target/cargo-timings/cargo-timing.html`。这里是一个[在线示例](https://blog.rust-lang.org/images/2022-04-07-timing.html)。该报告在你需要提升构建速度时会非常有用,更多的信息请[查看文档](https://doc.rust-lang.org/nightly/cargo/reference/timings.html)。
## Cargo Feature 的新语法
> 关于 Cargo Features ,强烈推荐大家看看 [Cargo 使用指南](https://course.rs/cargo/reference/features/intro.html),可能是目前最好的中文翻译版本。
新版本为 Cargo Features 引入了两个新的语法: 命名空间 ( Namespaced )和弱依赖,它们可以让 features 跟可选依赖进行更好的交互。
Cargo 支持[可选依赖](https://course.rs/cargo/reference/features/intro.html#可选依赖)已经很久了,例如以下代码所示:
```toml
[dependencies]
jpeg-decoder = { version = "0.1.20", default-features = false, optional = true }
[features]
# 通过开启 jpeg-decoder 依赖的 "rayon` feture来启用并行化处理
parallel = ["jpeg-decoder/rayon"]
```
这个例子有两点值得注意:
- 可选依赖 `jpeg-decoder` 隐式地定义了一个同名的 feature当启用 `jpeg-decoder` feature 时将同时启用 `jpeg-decoder`
- `"jpeg-decoder/rayon"` 语法会启用 `jpeg-decoder` 依赖,并且还会启用 `jpeg-decoder` 依赖的 `rayon` feature
而命名空间正是为了处理第一个问题而出现的。新版本中,我们可以在 `[features]` 中使用 `dep:` 前缀来显式地引用一个可选的依赖。再无需像第一点一样:先隐式的将可选依赖暴露为一个 feature再通过 feature 来启用它。
这样一来,我们将能更好的定义可选依赖所对应的 feture包括将可选依赖隐藏在一个更具描述性的 feature 名称后面。
弱依赖用于处理第二点: 根据第二点,`optional-dependency/feature-name` 必定会启用 `optional-dependency` 这个可选依赖。然而在一些场景中,我们只希望在其它 features 已经启用了可选依赖 `optional-dependency` 时才去启用 `feature-name` 这个 feature。
从 1.60 开始,我们可以使用 `"package-name?/feature-name"` 这种带有 `?` 形式的语法: 只有当其它项已经启用了可选依赖 `package-name` 的情况下才去开启给定的 feature `feature-name`
> 译者注:简单来说,要启用 `feature` 必须需要别人先启用了其前置的可选依赖,再也无法像之前的第二点一样,既能开启可选依赖,又能启用 feature。
例如,我们希望为自己的库增加一些序列化功能,它需要开启某个可选依赖中的指定 feature可以这么做:
```toml
[dependencies]
serde = { version = "1.0.133", optional = true }
rgb = { version = "0.8.25", optional = true }
[features]
serde = ["dep:serde", "rgb?/serde"]
```
这里定义了以下关系:
1. 开启 `serde` feature 将启用可选的 `serde` 依赖
2. 只有当 `rgp` 依赖在其它地方已经被启用后,此处才能启用 `rgb``serde` feature
## 增量编译重启开启
在 [1.59 更新说明中](https://course.rs/appendix/rust-versions/1.59.html),我们有提到因为某些问题,增量编译被默认关闭了,现在官方修复了其中一些,并且确认目前的状态不会再影响用户的使用,因此在 1.60 版本中,增量编译又重新默认开启了。
## Instant 单调性保证
> 译者注Instant 可以获取当前的时间,因此保证其单调增长是非常重要的,例如 uuid 的生成往往依赖于时间戳的单调增长,一旦时间回退,就可能出现 uuid 重复的情况。
在目前所有的平台上,`Instant` 会去尝试使用系统提供的 API 来保证单调性行为( 目前主要针对 tier 1 的平台 )。然而在实际场景中这种单调性偶尔会因为硬件、虚拟化或操作系统bug 等原因而失效。
为了解决这些失效或是平台没有提供 API 的情况,`Instant::duration_since`, `Instant::elapsed``Instant::sub` 现在饱和为零( 这里不太好翻译,原文是 now saturate to zero大概意思是非负)。而在老版本中,这种时间回退的情况会导致 panic。
`Instant::checked_duration_since` 也可以用于检测和处理单调性失败或 `Instants` 的减法顺序不正确的情况。
但是目前的解决方法会遮掩一些错误的发生因此在未来版本中Rust 可能会重新就某些场景引入 panic 机制。
在 1.60 版本前,单调性主要通过标准库的互斥锁 Mutex 或原子性 atomic 来保证,但是在 `Instant::now()` 调用频繁时,可能会导致明显的性能问题。

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

@ -18,8 +18,8 @@
由于并发编程在现代社会非常重要因此每个主流语言都对自己的并发模型进行过权衡取舍和精心设计Rust 语言也不例外。下面的列表可以帮助大家理解不同并发模型的取舍:
- **OS 线程**, 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](../advnce/../advance/concurrency-with-threads/concurrency-parallelism.md)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够
- **事件驱动(Event driven)**, 这个名词你可能比较陌生,如果说事件驱动常常跟回调( Callback )一起使用,相信大家就恍然大悟了。这种模型性能相当的好,但最大的问题就是存在回调地狱的风险:非线性的控制流和结果处理导致了数据流向和错误传播变得难以掌控,还会导致代码可维护性和可读性的大幅降低,大名鼎鼎的 JS 曾经就存在回调地狱。
- **OS 线程**, 它最简单,也无需改变任何编程模型(业务/代码逻辑),因此非常适合作为语言的原生并发模型,我们在[多线程章节](https://course.rs/advance/concurrency-with-threads/concurrency-parallelism.html)也提到过Rust 就选择了原生支持线程级的并发编程。但是,这种模型也有缺点,例如线程间的同步将变得更加困难,线程间的上下文切换损耗较大。使用线程池在一定程度上可以提升性能,但是对于 IO 密集的场景来说,线程池还是不够。
- **事件驱动(Event driven)**, 这个名词你可能比较陌生,如果说事件驱动常常跟回调( Callback )一起使用,相信大家就恍然大悟了。这种模型性能相当的好,但最大的问题就是存在回调地狱的风险:非线性的控制流和结果处理导致了数据流向和错误传播变得难以掌控,还会导致代码可维护性和可读性的大幅降低,大名鼎鼎的 `JavaScript` 曾经就存在回调地狱。
- **协程(Coroutines)** 可能是目前最火的并发模型,`Go` 语言的协程设计就非常优秀,这也是 `Go` 语言能够迅速火遍全球的杀手锏之一。协程跟线程类似,无需改变编程模型,同时,它也跟 `async` 类似,可以支持大量的任务并发运行。但协程抽象层次过高,导致用户无法接触到底层的细节,这对于系统编程语言和自定义异步运行时是难以接受的
- **actor 模型**是 erlang 的杀手锏之一,它将所有并发计算分割成一个一个单元,这些单元被称为 `actor` , 单元之间通过消息传递的方式进行通信和数据传递,跟分布式系统的设计理念非常相像。由于 `actor` 模型跟现实很贴近,因此它相对来说更容易实现,但是一旦遇到流控制、失败重试等场景时,就会变得不太好用
- **async/await** 该模型性能高,还能支持底层编程,同时又像线程和协程那样无需过多的改变编程模型,但有得必有失,`async` 模型的问题就是内部实现机制过于复杂,对于用户来说,理解和使用起来也没有线程和协程简单,好在前者的复杂性开发者们已经帮我们封装好,而理解和使用起来不够简单,正是本章试图解决的问题。
@ -36,7 +36,7 @@
- **Future 在 Rust 中是惰性的**,只有在被轮询(`poll`)时才会运行, 因此丢弃一个 `future` 会阻止它未来再被运行, 你可以将`Future`理解为一个在未来某个时间点被调度执行的任务。
- **Async 在 Rust 中使用开销是零** 意味着只有你能看到的代码(自己的代码)才有性能损耗,你看不到的(`async` 内部实现)都没有性能损耗,例如,你可以无需分配任何堆内存、也无需任何动态分发来使用 `async` 这对于热点路径的性能有非常大的好处正是得益于此Rust 的异步编程性能才会这么高。
- **Rust 没有内置异步调用所必须的运行时**但是无需担心Rust 社区生态中已经提供了非常优异的运行时实现,例如大明星 [`tokio`](https://tokio.rs)
- **运行时同时支持单线程和多线程**,这两者拥有各自的优缺点, 稍后会讲
- **运行时同时支持单线程和多线程**,这两者拥有各自的优缺点稍后会讲
#### Rust: async vs 多线程
@ -54,6 +54,13 @@
总之,`async`编程并没有比多线程更好,最终还是根据你的使用场景作出合适的选择,如果无需高并发,或者也不在意线程切换带来的性能损耗,那么多线程使用起来会简单、方便的多!最后再简单总结下:
> 若大家使用 tokio那 CPU 密集的任务尤其需要用线程的方式去处理,例如使用 `spawn_blocking` 创建一个阻塞的线程取完成相应 CPU 密集任务。
>
> 至于具体的原因不仅是上文说到的那些还有一个是tokio 是协作式地调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸着着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
>
> 而使用 `spawn_blocking` 后,会创建一个单独的 OS 线程,该线程并不会被 tokio 所调度( 被 OS 所调度 ),因此它所执行的 CPU 密集任务也不会导致 tokio 调度的那些异步任务被饿死
- 有大量 `IO` 任务需要并发运行时,选 `async` 模型
- 有部分 `IO` 任务需要并发运行时,选多线程,如果想要降低线程创建和销毁的开销,可以使用线程池
- 有大量 `CPU` 密集任务需要并行运行时,例如并行计算,选多线程模型,且让线程数等于或者稍大于 `CPU` 核心数
@ -234,7 +241,7 @@ warning: unused implementer of `futures::Future` that must be used
hello, world!
```
不出所料,`main`函数中的`future`我们通过`block_on`函数进行了运行,但是这里的`hello_cat`返回的`Future`却没有任何人去执行它,不过好在编译器友善的给出了提示:`futures do nothing unless you .await or poll them `,两种解决方法:使用`.await`语法或者对`Future`进行轮询(`poll`)。
不出所料,`main`函数中的`future`我们通过`block_on`函数进行了运行,但是这里的`hello_cat`返回的`Future`却没有任何人去执行它,不过好在编译器友善的给出了提示:```futures do nothing unless you `.await` or poll them```,两种解决方法:使用`.await`语法或者对`Future`进行轮询(`poll`)。
后者较为复杂,暂且不表,先来使用`.await`试试:

@ -0,0 +1,5 @@
# 异步编程
接下来,我们将深入了解 async/await 的使用方式及背后的原理。
> 本章在内容上大量借鉴和翻译了原版英文书籍[Asynchronous Programming In Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html), 特此感谢

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

@ -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` 就是一个自引用结构体。
如果不移动任何值,那么上面的例子将没有任何问题,例如:

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

@ -0,0 +1,11 @@
# Rust 异步编程
在艰难的学完 Rust 入门和进阶所有的 70 个章节后,我们终于来到了这里。假如之前攀登的是珠穆朗玛峰,那么现在攀登的就是乔戈里峰( 比珠峰还难攀爬... )。
如果你想开发 Web 服务器、数据库驱动、消息服务等需要高并发的服务,那么本章的内容将值得认真对待和学习,将从以下方面深入讲解 Rust 的异步编程:
- Rust 异步编程的通用概念介绍
- Future 以及异步任务调度
- async/await 和 Pin/Unpin
- 异步编程常用的三方库
- tokio 库
- 一些示例

@ -76,6 +76,14 @@ Rust 语言的安全可靠性顺理成章的影响了 `tokio` 的可靠性,曾
- 读取大量的文件, 读取文件的瓶颈主要在于操作系统,因为 OS 没有提供异步文件读取接口,大量的并发并不会提升文件读取的并行性能,反而可能会造成不可忽视的性能损耗,因此建议使用线程(或线程池)的方式
- 发送 HTTP 请求,`tokio` 的优势是给予你并发处理大量任务的能力,对于这种轻量级 HTTP 请求场景,`tokio` 除了增加你的代码复杂性,并无法带来什么额外的优势。因此,对于这种场景,你可以使用 [`reqwest`](https://github.com/seanmonstar/reqwest) 库,它会更加简单易用。
> 若大家使用 tokio那 CPU 密集的任务尤其需要用线程的方式去处理,例如使用 `spawn_blocking` 创建一个阻塞的线程取完成相应 CPU 密集任务。
>
> 原因是tokio 是协作式地调度器,如果某个 CPU 密集的异步任务是通过 tokio 创建的那理论上来说该异步任务需要跟其它的异步任务交错执行最终大家都得到了执行皆大欢喜。但实际情况是CPU 密集的任务很可能会一直霸着着 CPU此时 tokio 的调度方式决定了该任务会一直被执行,这意味着,其它的异步任务无法得到执行的机会,最终这些任务都会因为得不到资源而饿死。
>
> 而使用 `spawn_blocking` 后,会创建一个单独的 OS 线程,该线程并不会被 tokio 所调度( 被 OS 所调度 ),因此它所执行的 CPU 密集任务也不会导致 tokio 调度的那些异步任务被饿死
## 总结
离开三方开源社区提供的异步运行时, `async/await` 什么都不是,甚至还不如一堆破铜烂铁,除非你选择根据自己的需求手撸一个。

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

@ -1,14 +0,0 @@
# 异步编程
在艰难的学完 Rust 入门和进阶所有的 55 个章节后,我们终于来到了这里。假如之前攀登的是珠穆拉玛峰,那么现在攀登的就是乔戈里峰( 比珠峰还难攀爬... ),本章将学习的内容是关于 async 异步编程。
如果你想开发 Web 服务器、数据库驱动、消息服务等需要高并发的服务,那么本章的内容将值得认真对待和学习,将从以下方面深入讲解 Rust 的异步编程:
- Rust 异步编程的通用概念介绍
- Future 以及异步任务调度
- async/await 和 Pin/Unpin
- 异步编程常用的三方库
- tokio 库
- 一些示例
> 本章在内容上大量借鉴和翻译了原版英文书籍[Asynchronous Programming In Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html), 特此感谢

@ -63,7 +63,7 @@ fn main() {
只能说,再不起眼的东西,都有其用途,在目前为止的学习过程中,大家已经看到过很多次 `fn main()` 函数的使用吧?那么这个函数返回什么呢?
没错, `main` 函数就返回这个单元类型 `()`,你不能说 `main` 函数无返回值,因为没有返回值的函数在 Rust 中是有单独的定义的:`发散函数`,顾名思义,无法收敛的函数。
没错, `main` 函数就返回这个单元类型 `()`,你不能说 `main` 函数无返回值,因为没有返回值的函数在 Rust 中是有单独的定义的:`发散函数( diverge function )`,顾名思义,无法收敛的函数。
例如常见的 `println!()` 的返回值也是单元类型 `()`

@ -168,11 +168,11 @@ error[E0308]: mismatched types // 类型不匹配
| - help: consider removing this semicolon
```
还记得我们在[语句与表达式](./statement-expression.md)中讲过的吗?只有表达式能返回值,而 `;` 结尾的是语句,在 Rust 中,一定要严格区分**表达式**和**语句**的区别,这个在其它语言中往往是被忽视的点。
还记得我们在[语句与表达式](https://course.rs/basic/base-type/statement-expression.html)中讲过的吗?只有表达式能返回值,而 `;` 结尾的是语句,在 Rust 中,一定要严格区分**表达式**和**语句**的区别,这个在其它语言中往往是被忽视的点。
##### 永不返回的函数`!`
##### 永不返回的发散函数 `!`
当用 `!` 作函数返回类型的时候,表示该函数永不返回,特别的,这种语法往往用做会导致程序崩溃的函数:
当用 `!` 作函数返回类型的时候,表示该函数永不返回( diverge function ),特别的,这种语法往往用做会导致程序崩溃的函数:
```rust
fn dead_end() -> ! {

@ -1,4 +1,4 @@
# 语句和表达式
# 语句和表达式
Rust 的函数体是由一系列语句组成,最后由一个表达式来返回值,例如:
@ -55,7 +55,7 @@ error[E0658]: `let` expressions in this position are experimental
```
以上的错误告诉我们 `let` 是语句,不是表达式,因此它不返回值,也就不能给其它变量赋值。但是该错误还透漏了一个重要的信息, `let` 作为表达式已经是试验功能了,也许不久的将来,我们在 [`stable rust`](../../appendix/rust-version.md) 下可以这样使用。
以上的错误告诉我们 `let` 是语句,不是表达式,因此它不返回值,也就不能给其它变量赋值。但是该错误还透漏了一个重要的信息, `let` 作为表达式已经是试验功能了,也许不久的将来,我们在 [`stable rust`](https://course.rs/appendix/rust-version.html) 下可以这样使用。
## 表达式
@ -92,4 +92,3 @@ fn main() {
> [Rust By Practice](https://zh.practice.rs/basic-types/statements-expressions.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

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

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

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

@ -323,7 +323,7 @@ impl<T> AsyncReceiver<T> {
除了跳转到标准库,你还可以通过指定具体的路径跳转到自己代码或者其它库的指定项,例如在 `lib.rs` 中添加以下代码:
```rust
mod a {
pub mod a {
/// `add_one` 返回一个[`Option`]类型
/// 跳转到[`crate::MySpecialFormatter`]
pub fn add_one(x: i32) -> Option<i32> {
@ -331,7 +331,7 @@ mod a {
}
}
struct MySpecialFormatter;
pub struct MySpecialFormatter;
```
使用 `crate::MySpecialFormatter` 这种路径就可以实现跳转到 `lib.rs` 中定义的结构体上。
@ -387,7 +387,7 @@ pub struct BigY;
Created binary (application) `art` package
```
系统提示我们创建了一个二进制 `Package`,根据[之前章节](./crate-module/crate.md)学过的内容,可以知道该 `Package` 包含一个同名的二进制包:包名为 `art`,包根为 `src/main.rs`,该包可以编译成二进制然后运行。
系统提示我们创建了一个二进制 `Package`,根据[之前章节](https://course.rs/basic/crate-module/crate.html)学过的内容,可以知道该 `Package` 包含一个同名的二进制包:包名为 `art`,包根为 `src/main.rs`,该包可以编译成二进制然后运行。
现在,在 `src` 目录下创建一个 `lib.rs` 文件,同样,根据之前学习的知识,创建该文件等于又创建了一个库类型的包,包名也是 `art`,包根为 `src/lib.rs`,该包是是库类型的,因此往往作为依赖库被引入。
@ -429,7 +429,7 @@ pub mod utils {
/// ```rust
/// use art::utils::mix;
/// use art::kinds::{PrimaryColor,SecondaryColor};
/// assert_eq!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green)
/// assert!(matches!(mix(PrimaryColor::Yellow, PrimaryColor::Blue), SecondaryColor::Green));
/// ```
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
SecondaryColor::Green

@ -22,7 +22,7 @@ fn main() {
}
```
数组语法跟 JavaScript 很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组 `array` 是存储在栈上**,性能也会非常优秀。与此对应,**动态数组 `Vector` 是存储在堆上**,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组 Vector](../collections/vector.md)。
数组语法跟 JavaScript 很像,也跟大多数编程语言很像。由于它的元素类型大小固定,且长度也是固定,因此**数组 `array` 是存储在栈上**,性能也会非常优秀。与此对应,**动态数组 `Vector` 是存储在堆上**,因此长度可以动态改变。当你不确定是使用数组还是动态数组时,那就应该使用后者,具体见[动态数组 Vector](https://course.rs/basic/collections/vector.html)。
举个例子,在需要知道一年中各个月份名称的程序中,你很可能希望使用的是数组而不是动态数组。因为月份是固定的,它总是只包含 12 个元素:
@ -113,7 +113,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
## 数组切片
在之前的[章节](./string-slice.md#切片slice),我们有讲到 `切片` 这个概念,它允许你引用集合中的部分连续片段,而不是整个集合,对于数组也是,数组切片允许我们引用数组的一部分:
在之前的[章节](https://course.rs/basic/compound-type/string-slice.html#切片slice),我们有讲到 `切片` 这个概念,它允许你引用集合中的部分连续片段,而不是整个集合,对于数组也是,数组切片允许我们引用数组的一部分:
```rust
let a: [i32; 5] = [1, 2, 3, 4, 5];
@ -166,13 +166,12 @@ fn main() {
做个总结,数组虽然很简单,但是其实还是存在几个要注意的点:
- **数组类型容易跟数组切片混淆**[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译得知,因此不能用[T;n]的形式去描述
- **数组类型容易跟数组切片混淆**[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译得知,因此不能用[T;n]的形式去描述
- `[u8; 3]`和`[u8; 4]`是不同的类型,数组的长度也是类型的一部分
- **在实际开发中,使用最多的是数组切片[T]**,我们往往通过引用的方式去使用`&[T]`,因为后者有固定的类型大小
至此,关于数据类型部分,我们已经全部学完了,对于 Rust 学习而言,我们也迈出了坚定的第一步,后面将开始更高级特性的学习。未来如果大家有疑惑需要检索知识,一样可以继续回顾过往的章节,因为本书不仅仅是一门 Rust 的教程,还是一本厚重的 Rust 工具书。
## 课后练习
> [Rust By Practice](https://zh.practice.rs/compound-types/array.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。
> [Rust By Practice](https://zh.practice.rs/compound-types/array.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

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

@ -2,7 +2,7 @@
行百里者半五十,欢迎大家来到这里,虽然还不到中点,但是已经不远了。如果说之前学的基础数据类型是原子,那么本章将讲的数据类型可以认为是分子。
本章的重点在复合类型上,顾名思义,复合类型是由其它类型组合而成的,最典型的就是结构体 `struct` 和枚举 `enum`。例如平面上的一个点 `point(x,y)`,它由两个数值类型的值 `x``y` 组合而来。我们无法单独去维护这两个数值,因为单独一个 `x` 或者 `y` 是含义不完整的,无法标识平面上的一个点,应该把它们看作一个整体去理解和处理。
本章的重点在复合类型上,顾名思义,复合类型是由其它类型组合而成的,最典型的就是结构体 `struct` 和枚举 `enum`。例如平面上的一个点 `point(x, y)`,它由两个数值类型的值 `x``y` 组合而来。我们无法单独去维护这两个数值,因为单独一个 `x` 或者 `y` 是含义不完整的,无法标识平面上的一个点,应该把它们看作一个整体去理解和处理。
来看一段代码,它使用我们之前学过的内容来构建文件操作:

@ -1,6 +1,6 @@
# 字符串
在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,但是如果你带着同样的想法来学 Rust我保证绝对会栽跟头 **因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。
在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,但是如果你带着同样的想法来学 Rust我保证绝对会栽跟头**因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。
首先来看段很简单的代码:
@ -49,7 +49,7 @@ let world = &s[6..11];
`hello` 没有引用整个 `String s`,而是引用了 `s` 的一部分内容,通过 `[0..5]` 的方式来指定。
这就是创建切片的语法,使用方括号包括的一个序列: **[开始索引..终止索引]**,其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 `右半开区间`。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 `终止索引` - `开始索引` 的方式计算得来的。
这就是创建切片的语法,使用方括号包括的一个序列**[开始索引..终止索引]**,其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 `右半开区间`。在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 `终止索引` - `开始索引` 的方式计算得来的。
对于 `let world = &s[6..11];` 来说,`world` 是一个切片,该切片的指针指向 `s` 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 `5` 个字节。
@ -95,7 +95,7 @@ let slice = &s[..];
> ```
>
> 因为我们只取 `s` 字符串的前两个字节,但是本例中每个汉字占用三个字节,因此没有落在边界处,也就是连 `中` 字都取不完整,此时程序会直接崩溃退出,如果改成 `&s[0..3]`,则可以正常通过编译。
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf8-字符串)
> 因此,当你需要对字符串做切片索引操作时,需要格外小心这一点, 关于该如何操作 UTF-8 字符串,参见[这里](#操作-utf-8-字符串)
字符串切片的类型标识是 `&str`,因此我们可以这样声明一个函数,输入 `String` 类型,返回它的切片: `fn first_word(s: &String) -> &str `
@ -178,69 +178,6 @@ Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引
除了 `String` 类型的字符串Rust 的标准库还提供了其他类型的字符串,例如 `OsString` `OsStr` `CsString` 和` CsStr` 等,注意到这些名字都以 `String` 或者 `Str` 结尾了吗?它们分别对应的是具有所有权和被借用的变量。
#### 操作字符串
由于 `String` 是可变字符串,因此我们可以对它进行创建、增删操作,下面的代码汇总了相关的操作方式:
```rust
fn main() {
// 创建一个空String
let mut s = String::new();
// 将&str类型的"hello,world"添加到s中
s.push_str("hello,world");
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"hello,world!"
assert_eq!(s,"hello,world!");
// 从现有的&str切片创建String类型
let mut s = "hello,world".to_string();
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"hello,world!"
assert_eq!(s,"hello,world!");
// 从现有的&str切片创建String类型
// String与&str都是UTF-8编码因此支持中文
let mut s = String::from("你好,世界");
// 将字符'!'推入s中
s.push('!');
// 最后s的内容是"你好,世界!"
assert_eq!(s,"你好,世界!");
let s1 = String::from("hello,");
let s2 = String::from("world!");
// 在下句中s1的所有权被转移走了因此后面不能再使用s1
let s3 = s1 + &s2;
assert_eq!(s3,"hello,world!");
// 下面的语句如果去掉注释,就会报错
// println!("{}",s1);
}
```
在上面代码中,有一处需要解释的地方,就是使用 `+` 来对字符串进行相加操作, 这里之所以使用 `s1 + &s2` 的形式,是因为 `+` 使用了 `add` 方法,该方法的定义类似:
```rust
fn add(self, s: &str) -> String {
```
因为该方法涉及到更复杂的特征功能,因此我们这里简单说明下, `self``String` 类型的字符串 `s1`,该函数说明,只能将 `&str` 类型的字符串切片添加到 `String` 类型的 `s1` 上,然后返回一个新的 `String` 类型,所以 `let s3 = s1 + &s2;` 就很好解释了,将 `String` 类型的 `s1``&str` 类型的 `s2` 进行相加,最终得到 `String` 类型的 `s3`
由此可推,以下代码也是合法的:
```rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// String = String + &str + &str + &str + &str
let s = s1 + "-" + &s2 + "-" + &s3;
```
`String + &str`返回一个 `String`,然后再继续跟一个 `&str` 进行 `+` 操作,返回一个 `String` 类型,不断循环,最终生成一个 `s`,也是 `String` 类型。
在上面代码中,我们做了一个有些难以理解的 `&String` 操作,下面来展开讲讲。
## String 与 &str 的转换
在之前的代码中,已经见到好几种从 `&str` 类型生成 `String` 类型的操作:
@ -285,7 +222,7 @@ fn say_hello(s: &str) {
#### 深入字符串内部
字符串的底层的数据存储格式实际上是[ `u8` ],一个字节数组。对于 `let hello = String::from("Hola");` 这行代码来说, `hello` 的长度是 `4` 个字节,因为 `"hola"` 中的每个字母在 UTF-8 编码中仅占用 1 个字节,但是对于下面的代码呢?
字符串的底层的数据存储格式实际上是[ `u8` ],一个字节数组。对于 `let hello = String::from("Hola");` 这行代码来说,`hello` 的长度是 `4` 个字节,因为 `"hola"` 中的每个字母在 UTF-8 编码中仅占用 1 个字节,但是对于下面的代码呢?
```rust
let hello = String::from("中国人");
@ -339,7 +276,355 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
因此在通过索引区间来访问字符串时,**需要格外的小心**,一不注意,就会导致你程序的崩溃!
## 操作 UTF8 字符串
## 操作字符串
由于 `String` 是可变字符串,下面介绍 Rust 字符串的修改,添加,删除等常用方法:
#### 追加 (Push)
在字符串尾部可以使用 `push()` 方法追加字符 `char`,也可以使用 `push_str()` 方法追加字符串字面量。这两个方法都是**在原有的字符串上追加,并不会返回新的字符串**。由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即**字符串变量必须由 `mut` 关键字修饰**。
示例代码如下:
```rust
fn main() {
let mut s = String::from("Hello ");
s.push('r');
println!("追加字符 push() -> {}", s);
s.push_str("ust!");
println!("追加字符串 push_str() -> {}", s);
}
```
代码运行结果:
```console
追加字符 push() -> Hello r
追加字符串 push_str() -> Hello rust!
```
#### 插入 (Insert)
可以使用 `insert()` 方法插入单个字符 `char`,也可以使用 `insert_str()` 方法插入字符串字面量,与 `push()` 方法不同,这俩方法需要传入两个参数,第一个参数是字符(串)插入插入位置的索引,第二个参数是要插入的字符(串),索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要**修改原来的字符串**,则该字符串必须是可变的,即**字符串变量必须由 `mut` 关键字修饰**。
示例代码如下:
```rust
fn main() {
let mut s = String::from("Hello rust!");
s.insert(5, ',');
println!("插入字符 insert() -> {}", s);
s.insert_str(6, " I like");
println!("插入字符串 insert_str() -> {}", s);
}
```
代码运行结果:
```console
插入字符 insert() -> Hello, rust!
插入字符串 insert_str() -> Hello, I like rust!
```
#### 替换 (Replace)
如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用 `replace()` 方法。与替换有关的方法有三个。
1、`replace`
该方法可适用于 `String``&str` 类型。`replace()` 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。**该方法是返回一个新的字符串,而不是操作原来的字符串**。
示例代码如下:
```rust
fn main() {
let string_replace = String::from("I like rust. Learning rust is my favorite!");
let new_string_replace = string_replace.replace("rust", "RUST");
dbg!(new_string_replace);
}
```
代码运行结果:
```console
new_string_replace = "I like RUST. Learning RUST is my favorite!"
```
2、`replacen`
该方法可适用于 `String``&str` 类型。`replacen()` 方法接收三个参数,前两个参数与 `replace()` 方法一样,第三个参数则表示替换的个数。**该方法是返回一个新的字符串,而不是操作原来的字符串**。
示例代码如下:
```rust
fn main() {
let string_replace = "I like rust. Learning rust is my favorite!";
let new_string_replacen = string_replace.replacen("rust", "RUST", 1);
dbg!(new_string_replacen);
}
```
代码运行结果:
```console
new_string_replacen = "I like RUST. Learning rust is my favorite!"
```
3、`replace_range`
该方法仅适用于 `String` 类型。`replace_range` 接收两个参数第一个参数是要替换字符串的范围Range第二个参数是新的字符串。**该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 `mut` 关键字修饰**。
示例代码如下:
```rust
fn main() {
let mut string_replace_range = String::from("I like rust!");
string_replace_range.replace_range(7..8, "R");
dbg!(string_replace_range);
}
```
代码运行结果:
```console
string_replace_range = "I like Rust!"
```
#### 删除 (Delete)
与字符串删除相关的方法有 4 个,他们分别是 `pop()``remove()``truncate()``clear()`。这四个方法仅适用于 `String` 类型。
1、 `pop` —— 删除并返回字符串的最后一个字符
**该方法是直接操作原来的字符串**。但是存在返回值,其返回值是一个 `Option` 类型,如果字符串为空,则返回 `None`
示例代码如下:
```rust
fn main() {
let mut string_pop = String::from("rust pop 中文!");
let p1 = string_pop.pop();
let p2 = string_pop.pop();
dbg!(p1);
dbg!(p2);
dbg!(string_pop);
}
```
代码运行结果:
```console
p1 = Some(
'!',
)
p2 = Some(
'文',
)
string_pop = "rust pop 中"
```
2、 `remove` —— 删除并返回字符串中指定位置的字符
**该方法是直接操作原来的字符串**。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。`remove()` 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
示例代码如下:
```rust
fn main() {
let mut string_remove = String::from("测试remove方法");
println!(
"string_remove 占 {} 个字节",
std::mem::size_of_val(string_remove.as_str())
);
// 删除第一个汉字
string_remove.remove(0);
// 下面代码会发生错误
// string_remove.remove(1);
// 直接删除第二个汉字
// string_remove.remove(3);
dbg!(string_remove);
}
```
代码运行结果:
```console
string_remove 占 18 个字节
string_remove = "试remove方法"
```
3、`truncate` —— 删除字符串中从指定位置开始到结尾的全部字符
**该方法是直接操作原来的字符串**。无返回值。该方法 `truncate()` 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
示例代码如下:
```rust
fn main() {
let mut string_truncate = String::from("测试truncate");
string_truncate.truncate(3);
dbg!(string_truncate);
}
```
代码运行结果:
```console
string_truncate = "测"
```
4、`clear` —— 清空字符串
**该方法是直接操作原来的字符串**。调用后,删除字符串中的所有字符,相当于 `truncate()` 方法参数为 0 的时候。
示例代码如下:
```rust
fn main() {
let mut string_clear = String::from("string clear");
string_clear.clear();
dbg!(string_clear);
}
```
代码运行结果:
```console
string_clear = ""
```
#### 连接 (Catenate)
1、使用 `+` 或者 `+=` 连接字符串
使用 `+` 或者 `+=` 连接字符串要求右边的参数必须为字符串的切片引用Slice)类型。其实当调用 `+` 的操作符时,相当于调用了 `std::string` 标准库中的 [`add()`](https://doc.rust-lang.org/std/string/struct.String.html#method.add) 方法,这里 `add()` 方法的第二个参数是一个引用的类型。因此我们在使用 `+` 必须传递切片引用类型。不能直接传递 `String` 类型。**`+` 和 `+=` 都是返回一个新的字符串。所以变量声明可以不需要 `mut` 关键字修饰**。
示例代码如下:
```rust
fn main() {
let string_append = String::from("hello ");
let string_rust = String::from("rust");
// &string_rust会自动解引用为&str
let result = string_append + &string_rust;
let mut result = result + "!";
result += "!!!";
println!("连接字符串 + -> {}", result);
}
```
代码运行结果:
```console
连接字符串 + -> hello rust!!!!
```
`add()` 方法的定义:
```rust
fn add(self, s: &str) -> String
```
因为该方法涉及到更复杂的特征功能,因此我们这里简单说明下:
```rust
fn main() {
let s1 = String::from("hello,");
let s2 = String::from("world!");
// 在下句中s1的所有权被转移走了因此后面不能再使用s1
let s3 = s1 + &s2;
assert_eq!(s3,"hello,world!");
// 下面的语句如果去掉注释,就会报错
// println!("{}",s1);
}
```
`self``String` 类型的字符串 `s1`,该函数说明,只能将 `&str` 类型的字符串切片添加到 `String` 类型的 `s1` 上,然后返回一个新的 `String` 类型,所以 `let s3 = s1 + &s2;` 就很好解释了,将 `String` 类型的 `s1``&str` 类型的 `s2` 进行相加,最终得到 `String` 类型的 `s3`
由此可推,以下代码也是合法的:
```rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
// String = String + &str + &str + &str + &str
let s = s1 + "-" + &s2 + "-" + &s3;
```
`String + &str`返回一个 `String`,然后再继续跟一个 `&str` 进行 `+` 操作,返回一个 `String` 类型,不断循环,最终生成一个 `s`,也是 `String` 类型。
`s1` 这个变量通过调用 `add()` 方法后,所有权被转移到 `add()` 方法里面, `add()` 方法调用后就被释放了,同时 `s1` 也被释放了。再使用 `s1` 就会发生错误。这里涉及到[所有权转移Move](https://course.rs/basic/ownership/ownership.html#转移所有权)的相关知识。
2、使用 `format!` 连接字符串
`format!` 这种方式适用于 `String``&str` 。`format!` 的用法与 `print!` 的用法类似,详见[格式化输出](https://course.rs/basic/formatted-output.html#printprintlnformat)。
示例代码如下:
```rust
fn main() {
let s1 = "hello";
let s2 = String::from("rust");
let s = format!("{} {}!", s1, s2);
println!("{}", s);
}
```
代码运行结果:
```console
hello rust!
```
## 字符串转义
我们可以通过转义的方式 `\` 输出 ASCII 和 Unicode 字符。
```rust
fn main() {
// 通过 \ + 字符的十六进制表示,转义输出一个字符
let byte_escape = "I'm writing \x52\x75\x73\x74!";
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
// \u 可以输出一个 unicode 字符
let unicode_codepoint = "\u{211D}";
let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";
println!(
"Unicode character {} (U+211D) is called {}",
unicode_codepoint, character_name
);
// 换行了也会保持之前的字符串格式
let long_string = "String literals
can span multiple lines.
The linebreak and indentation here ->\
<- can be escaped too!";
println!("{}", long_string);
}
```
当然,在某些情况下,可能你会希望保持字符串的原样,不要转义:
```rust
fn main() {
println!("{}", "hello \\x52\\x75\\x73\\x74");
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
println!("{}", raw_str);
// 如果字符串包含双引号,可以在开头和结尾加 #
let quotes = r#"And then I said: "There is no escape!""#;
println!("{}", quotes);
// 如果还是有歧义,可以继续增加,没有限制
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
println!("{}", longer_delimiter);
}
```
## 操作 UTF-8 字符串
前文提到了几种使用 UTF-8 字符串的方式,下面来一一说明。
@ -424,10 +709,14 @@ for b in "中国人".bytes() {
这个模式对编写 Rust 代码的方式有着深远的影响,在后面章节我们会进行更深入的介绍。
## 课后练习
> Rust By Practice支持代码在线编辑和运行并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。
> - [字符串](https://zh.practice.rs/compound-types/string.html)
> - [切片](https://zh.practice.rs/compound-types/slice.html)
> - [String](https://zh.practice.rs/collections/String.html)
- [字符串](https://zh.practice.rs/compound-types/string.html)
- [切片](https://zh.practice.rs/compound-types/slice.html)
- [String](https://zh.practice.rs/collections/String.html)
<hr />
## 引用资料
1. https://blog.csdn.net/a1595901624/article/details/119294443

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

@ -80,9 +80,9 @@ fn main() {
}
```
上面代码中引入了 `std::convert::TryInto` 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中**,我们在上面用到了 `try_into` 方法,因此需要引入对应的特征。但是 Rust 又提供了一个非常便利的办法,把最常用的标准库中的特征通过[`std::prelude`](std::convert::TryInto)模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
上面代码中引入了 `std::convert::TryInto` 特征,但是却没有使用它,可能有些同学会为此困惑,主要原因在于**如果你要使用一个特征的方法,那么你需要引入该特征到当前的作用域中**,我们在上面用到了 `try_into` 方法,因此需要引入对应的特征。但是 Rust 又提供了一个非常便利的办法,把最常用的标准库中的特征通过[`std::prelude`](https://course.rs/appendix/prelude.html)模块提前引入到当前作用域中,其中包括了 `std::convert::TryInto`,你可以尝试删除第一行的代码 `use ...`,看看是否会报错。
`try_into` 会尝试进行一次转换,并返回一个 `Result`,此时就可以对其进行相应的错误处理。由于我们的例子只是为了快速测试,因此使用了 `unwrap` 方法,该方法在发现错误时,会直接调用 `panic` 导致程序的崩溃退出,在实际项目中,请不要这么使用,具体见[panic](./exception-error.md#panic)部分。
`try_into` 会尝试进行一次转换,并返回一个 `Result`,此时就可以对其进行相应的错误处理。由于我们的例子只是为了快速测试,因此使用了 `unwrap` 方法,该方法在发现错误时,会直接调用 `panic` 导致程序的崩溃退出,在实际项目中,请不要这么使用,具体见[panic](https://course.rs/basic/result-error/panic.html#调用-panic)部分。
最主要的是 `try_into` 转换会捕获大类型向小类型转换时导致的溢出错误:
@ -275,11 +275,46 @@ impl<T> Clone for Container<T> {
- 这种转换永远都是未定义的
- 不,你不能这么做
- 不要多想,你没有那种幸运
4. 变形为一个未指定生命周期的引用会导致[无界生命周期](../advance/lifetime/advance.md)
4. 变形为一个未指定生命周期的引用会导致[无界生命周期](https://course.rs/advance/lifetime/advance.html)
5. 在复合类型之间互相变换时,你需要保证它们的排列布局是一模一样的!一旦不一样,那么字段就会得到不可预期的值,这也是未定义的行为,至于你会不会因此愤怒, **WHO CARES** ,你都用了变形了,老兄!
对于第 5 条,你该如何知道内存的排列布局是一样的呢?对于 `repr(C)` 类型和 `repr(transparent)` 类型来说,它们的布局是有着精确定义的。但是对于你自己的"普通却自信"的 Rust 类型 `repr(Rust)` 来说,它可不是有着精确定义的。甚至同一个泛型类型的不同实例都可以有不同的内存布局。 `Vec<i32>``Vec<u32>` 它们的字段可能有着相同的顺序,也可能没有。对于数据排列布局来说,**什么能保证,什么不能保证**目前还在 Rust 开发组的[工作任务](https://rust-lang.github.io/unsafe-code-guidelines/layout.html)中呢。
你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。 `mem::transmute_copy<T, U>` 才是真正的深渊,它比之前的还要更加危险和不安全。它从 `T` 类型中拷贝出 `U` 类型所需的字节数,然后转换成 `U``mem::transmute` 尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过 `U` 的尺寸若是比 `T` 大,会是一个未定义行为。
当然,你也可以通过原生指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。
当然,你也可以通过裸指针转换和 `unions` (todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。裸指针转换和 `unions` 也不是魔法,无法逃避上面说的规则。
`transmute` 虽然危险,但作为一本工具书,知识当然要全面,下面列举两个有用的 `transmute` 应用场景 :)。
- 将裸指针变成函数指针:
```rust
fn foo() -> i32 {
0
}
let pointer = foo as *const ();
let function = unsafe {
// 将裸指针转换为函数指针
std::mem::transmute::<*const (), fn() -> i32>(pointer)
};
assert_eq!(function(), 0);
```
- 延长生命周期,或者缩短一个静态生命周期寿命:
```rust
struct R<'a>(&'a i32);
// 将 'b 生命周期延长至 'static 生命周期
unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
std::mem::transmute::<R<'b>, R<'static>>(r)
}
// 将 'static 生命周期缩短至 'c 生命周期
unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> {
std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}
```
以上例子非常先进!但是是非常不安全的 Rust 行为!

@ -105,6 +105,6 @@ error: a bin target must be available for `cargo run`
- 基准性能测试 `benchmark` 文件:`benches` 目录下
- 项目示例:`examples` 目录下
这种目录结构基本上是 Rust 的标准目录结构,在 `github` 的大多数项目上,你都将看到它的身影。
这种目录结构基本上是 Rust 的标准目录结构,在 `GitHub` 的大多数项目上,你都将看到它的身影。
理解了包的概念,我们再来看看构成包的基本单元:模块。

@ -38,7 +38,7 @@ mod front_of_house {
## 模块树
在[上一节](./crate.md)中,我们提到过 `src/main.rs``src/lib.rs` 被称为包根(crate root),这个奇葩名称的来源(我不想承认是自己翻译水平太烂-,-)是由于这两个文件的内容形成了一个模块 `crate`,该模块位于包的树形结构(由模块组成的树形结构)的根部:
在[上一节](https://course.rs/basic/crate-module/crate.html)中,我们提到过 `src/main.rs``src/lib.rs` 被称为包根(crate root),这个奇葩名称的来源(我不想承认是自己翻译水平太烂-,-)是由于这两个文件的内容形成了一个模块 `crate`,该模块位于包的树形结构(由模块组成的树形结构)的根部:
```console
crate
@ -261,7 +261,7 @@ mod back_of_house {
## 使用 `self` 引用模块
`self` 其实就是引用自身模块中的项,也就是说和我们之前章节的代码类似,都调用同一模块中的内容,区别在于之章节中直接通过名称调用即可,而 `self`,你得多此一举:
`self` 其实就是引用自身模块中的项,也就是说和我们之前章节的代码类似,都调用同一模块中的内容,区别在于之章节中直接通过名称调用即可,而 `self`,你得多此一举:
```rust
fn serve_order() {

@ -6,7 +6,7 @@
## 基本引入方式
在 Rust 中,引入模块中的项有两种方式:[绝对路径和相对路径](./module.md#用路径引用模块),这两者在前面章节都有讲过,就不再赘述,先来看看使用绝对路径的引入方式。
在 Rust 中,引入模块中的项有两种方式:[绝对路径和相对路径](https://course.rs/basic/crate-module/module.html#用路径引用模块),这两者在前面章节都有讲过,就不再赘述,先来看看使用绝对路径的引入方式。
#### 绝对路径引入模块
@ -143,7 +143,7 @@ pub fn eat_at_restaurant() {
## 使用第三方包
之前我们一直在引入标准库模块或者自定义模块,现在来引入下第三方包中的模块,关于如何引入外部依赖,我们在 [Cargo 入门](../../first-try/cargo.md#package配置段落)中就有讲,这里直接给出操作步骤:
之前我们一直在引入标准库模块或者自定义模块,现在来引入下第三方包中的模块,关于如何引入外部依赖,我们在 [Cargo 入门](https://course.rs/first-try/cargo.html#package-配置段落)中就有讲,这里直接给出操作步骤:
1. 修改 `Cargo.toml` 文件,在 `[dependencies]` 区域添加一行:`rand = "0.8.3"`
2. 此时,如果你用的是 `VSCode``rust-analyzer` 插件该插件会自动拉取该库你可能需要等它完成后再进行下一步VSCode 左下角有提示)
@ -234,7 +234,7 @@ fn main() {
## 受限的可见性
在上一节中,我们学习了[可见性](./module.md#代码可见性)这个概念,这也是模块体系中最为核心的概念,控制了模块中哪些内容可以被外部看见,但是在实际使用时,光被外面看到还不行,我们还想控制哪些人能看,这就是 Rust 提供的受限可见性。
在上一节中,我们学习了[可见性](https://course.rs/basic/crate-module/module.html#代码可见性)这个概念,这也是模块体系中最为核心的概念,控制了模块中哪些内容可以被外部看见,但是在实际使用时,光被外面看到还不行,我们还想控制哪些人能看,这就是 Rust 提供的受限可见性。
例如,在 Rust 中,包是一个模块树,我们可以通过 `pub(crate) item;` 这种方式来实现:`item` 虽然是对外可见的,但是只在当前包内可见,外部包无法引用到该 `item`

@ -6,7 +6,7 @@
## 使用 if 来做分支控制
> if else 无处不在 - 鲁迅
> if else 无处不在 -- 鲁迅
但凡你能找到一门编程语言没有 `if else`,那么一定更要反馈给鲁迅,反正不是我说的:) 总之,只要你拥有其它语言的编程经验,就一定会有以下认知:`if else` **表达式**根据条件执行不同的代码分支:
@ -38,7 +38,7 @@ fn main() {
以上代码有以下几点要注意:
- **`if` 语句块是表达式**,这里我们使用 `if` 表达式的返回值来给 `number` 进行赋值:`number` 的值是 `5`
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](../appendix/expressions.md#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
- 用 `if` 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见[这里](https://course.rs/appendix/expressions.html#if表达式)),此处返回的 `5``6` 就是同一个类型,如果返回类型不一致就会报错
```console
error[E0308]: if and else have incompatible types
@ -56,7 +56,7 @@ error[E0308]: if and else have incompatible types
found type `&str`
```
#### 使用 else if 来处理多重条件
## 使用 else if 来处理多重条件
可以将 `else if``if`、`else` 组合在一起实现更复杂的条件分支判断:
@ -82,20 +82,20 @@ fn main() {
如果代码中有大量的 `else if ` 会让代码变得极其丑陋,不过不用担心,下一章的 `match` 专门用以解决多分支模式匹配的问题。
## 循环控制
# 循环控制
循环无处不在,上到数钱,下到数年,你能想象的很多场景都存在循环,因此它也是流程控制中最重要的组成部分之一。
在 Rust 语言中有三种循环方式:`for`、`while` 和 `loop`,其中 `for` 循环是 Rust 循环王冠上的明珠。
#### for 循环
## for 循环
`for` 循环是 Rust 的大杀器:
```rust
fn main() {
for i in 1..=5 {
println!("{}",i);
println!("{}", i);
}
}
```
@ -140,10 +140,10 @@ for item in &mut collection {
```rust
fn main() {
let a = [4,3,2,1];
let a = [4, 3, 2, 1];
// `.iter()` 方法把 `a` 数组变成一个迭代器
for (i,v) in a.iter().enumerate() {
println!("第{}个元素是{}",i+1,v);
for (i, v) in a.iter().enumerate() {
println!("第{}个元素是{}", i + 1, v);
}
}
```
@ -183,7 +183,7 @@ for item in collection {
由于 `for` 循环无需任何条件限制,也不需要通过索引来访问,因此是最安全也是最常用的,通过与下面的 `while` 的对比,我们能看到为什么 `for` 会更加安全。
#### `continue`
## `continue`
使用 `continue` 可以跳过当前当次的循环,开始下次的循环:
@ -192,7 +192,7 @@ for item in collection {
if i == 2 {
continue;
}
println!("{}",i);
println!("{}", i);
}
```
@ -203,7 +203,7 @@ for item in collection {
3
```
#### `break`
## `break`
使用 `break` 可以直接跳出当前整个循环:
@ -212,17 +212,17 @@ for item in collection {
if i == 2 {
break;
}
println!("{}",i);
println!("{}", i);
}
```
上面代码对 1 到 3 的序列进行迭代,在遇到值为 2 时的跳出整个循环,后面的循环不执行,输出如下:
上面代码对 1 到 3 的序列进行迭代,在遇到值为 2 时的跳出整个循环,后面的循环不执行,输出如下:
```console
1
```
#### while 循环
## while 循环
如果你需要一个条件来循环,当该条件为 `true` 时,继续循环,条件为 `false`,跳出循环,那么 `while` 就非常适用:
@ -262,7 +262,7 @@ fn main() {
if n > 5 {
break
}
println!("{}",n);
println!("{}", n);
n+=1;
}
@ -317,7 +317,7 @@ fn main() {
可以看出,`for` 并不会使用索引去访问数组,因此更安全也更简洁,同时避免 `运行时的边界检查`,性能更高。
#### loop 循环
## loop 循环
对于循环而言,`loop` 循环毋庸置疑,是适用面最高的,它可以适用于所有循环场景(虽然能用,但是在很多场景下, `for``while` 才是最优选择),因为 `loop` 就是一个简单的无限循环,你可以在内部实现逻辑通过 `break` 关键字来控制循环何时结束。
@ -368,8 +368,6 @@ fn main() {
- **break 可以单独使用,也可以带一个返回值**,有些类似 `return`
- **loop 是一个表达式**,因此可以返回一个值
## 课后练习
> [Rust By Practice](https://zh.practice.rs/flow-control.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -86,7 +86,7 @@ fn main() {
}
```
对于数值、字符串、数组,可以直接使用 `{:?}` 进行输出,但是对于结构体,需要[派生`Debug`](../appendix/derive.md)特征后,才能进行输出,总之很简单。
对于数值、字符串、数组,可以直接使用 `{:?}` 进行输出,但是对于结构体,需要[派生`Debug`](https://course.rs/appendix/derive.html)特征后,才能进行输出,总之很简单。
#### `Display` 特征
@ -168,7 +168,7 @@ fn main() {
#### 为外部类型实现 `Display` 特征
在 Rust 中,无法直接为外部类型实现外部特征,但是可以使用[`newtype`](./custom-type.md#newtype)解决此问题:
在 Rust 中,无法直接为外部类型实现外部特征,但是可以使用[`newtype`](https://course.rs/advance/into-types/custom-type.html#newtype)解决此问题:
```rust
struct Array(Vec<i32>);
@ -193,7 +193,7 @@ fn main() {
至此,关于 `{}``{:?}` 的内容已介绍完毕,下面让我们正式开始格式化输出的旅程。
## 指定位置参数
## 位置参数
除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如 `{1}`,表示用第二个参数替换该占位符(索引从 0 开始)
@ -207,7 +207,7 @@ fn main() {
}
```
## 带名称的变量
## 具名参数
除了像上面那样指定位置外,我们还可以为参数指定名称:

@ -44,9 +44,10 @@ fn main() {
```
> 注意
> 在上面的 `add` 函数中,不要为 `i+j` 添加 `;`,这会改变语法导致函数返回 `()` 而不是 `i32`,具体参见[语句和表达式](./base-type/statement-expression.md)
> 在上面的 `add` 函数中,不要为 `i+j` 添加 `;`,这会改变语法导致函数返回 `()` 而不是 `i32`,具体参见[语句和表达式](https://course.rs/basic/base-type/statement-expression.html)
有几点可以留意下:
- 字符串使用双引号 `""` 而不是单引号 `''`Rust 中单引号是留给单个字符类型(`char`)使用的
- Rust 使用 `{}` 来作为格式化输出占位符,其它语言可能使用的是 `%s``%d``%p` 等,由于 `println!` 会自动推导出具体的类型,因此无需手动指定

@ -139,7 +139,7 @@ fn main() {
这段代码创建了变量 `x``y`,与结构体 `p` 中的 `x``y` 字段相匹配。其结果是变量 `x``y` 包含结构体 `p` 中的值。
也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
也可以使用字面值作为结构体模式的一部分进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。
下文展示了固定某个字段的匹配方式:

@ -145,7 +145,7 @@ fn value_in_cents(coin: Coin) -> u8 {
上面代码中,在匹配 `Coin::Quarter(state)` 模式时,我们把它内部存储的值绑定到了 `state` 变量上,因此 `state` 变量就是对应的 `UsState` 枚举类型。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska))`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
例如有一个印了阿拉斯加州标记的 25 分硬币:`Coin::Quarter(UsState::Alaska)`, 它在匹配时,`state` 变量将被绑定 `UsState::Alaska` 的枚举值。
再来看一个更复杂的例子:
@ -241,7 +241,7 @@ error[E0004]: non-exhaustive patterns: `West` not covered // 非穷尽匹配,`
#### `_` 通配符
当我们不想在匹配的时候列出所有值的时候,可以使用 Rust 提供的一个特殊**模式**,例如,`u8` 可以拥有 0 到 255 的有效的值,但是我们只关心 `1、3、5 和 7` 这几个值,不想列出其它的 `0、2、4、6、8、9 一直到 255` 的值。那么, 我们不必一个一个列出所有值, 因为可以使用使用特殊的模式 `_` 替代:
当我们不想在匹配的时候列出所有值的时候,可以使用 Rust 提供的一个特殊**模式**,例如,`u8` 可以拥有 0 到 255 的有效的值,但是我们只关心 `1、3、5 和 7` 这几个值,不想列出其它的 `0、2、4、6、8、9 一直到 255` 的值。那么, 我们不必一个一个列出所有值, 因为可以使用特殊的模式 `_` 替代:
```rust
let some_u8_value = 0u8;
@ -264,7 +264,7 @@ match some_u8_value {
```rust
let v = Some(3u8);
match v{
match v {
Some(3) => println!("three"),
_ => (),
}
@ -323,7 +323,7 @@ assert!(matches!(bar, Some(x) if x > 2));
## 变量覆盖
无论是 `match` 还是 `if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:
无论是 `match` 还是 `if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:
```rust
fn main() {

@ -155,7 +155,7 @@ let Some(x) = some_option_value;
因为右边的值可能不为 `Some`,而是 `None`,这种时候就不能进行匹配,也就是上面的代码遗漏了 `None` 的匹配。
类似 `let``for`、`match` 都必须要求完全覆盖匹配,才能通过编译。
类似 `let``for`、`match` 都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )
但是对于 `if let`,就可以这样使用:
@ -165,4 +165,4 @@ if let Some(x) = some_option_value {
}
```
因为 `if let` 允许匹配一种模式,而忽略其余的模式。
因为 `if let` 允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )

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

@ -4,7 +4,7 @@
Rust 通过 `借用(Borrowing)` 这个概念来达成上述的目的,**获取变量的引用,称之为借用(borrowing)**。正如现实生活中,如果一个人拥有某样东西,你可以从他那里借来,当使用完毕后,也必须要物归原主。
### 引用与解引用
## 引用与解引用
常规引用是一个指针类型,指向了对象存储的内存地址。在下面代码中,我们创建一个 `i32` 值的引用 `y`,然后使用解引用运算符来解出 `y` 所使用的值:
@ -35,7 +35,7 @@ error[E0277]: can't compare `{integer}` with `&{integer}`
不允许比较整数与引用,因为它们是不同的类型。必须使用解引用运算符解出引用所指向的值。
### 不可变引用
## 不可变引用
下面的代码,我们用 `s1` 的引用作为参数传递给 `calculate_length` 函数,而不是把 `s1` 的所有权转移给该函数:
@ -102,7 +102,7 @@ error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` ref
正如变量默认不可变一样,引用指向的值默认也是不可变的,没事,来一起看看如何解决这个问题。
### 可变引用
## 可变引用
只需要一个小调整,即可修复上面代码的错误:
@ -118,9 +118,9 @@ fn change(some_string: &mut String) {
}
```
首先,声明 `s` 是可变类型,其次创建一个可变的引用 `&mut s` 和接受可变引用的函数 `some_string: &mut String`
首先,声明 `s` 是可变类型,其次创建一个可变的引用 `&mut s` 和接受可变引用参数 `some_string: &mut String` 的函数
##### 可变引用同时只能存在一个
#### 可变引用同时只能存在一个
不过可变引用并不是随心所欲、想用就用的,它有一个很大的限制: **同一作用域,特定数据只能有一个可变引用**
@ -173,7 +173,7 @@ let mut s = String::from("hello");
let r2 = &mut s;
```
##### 可变引用与不可变引用不能同时存在
#### 可变引用与不可变引用不能同时存在
下面的代码会导致一个错误:
@ -310,7 +310,6 @@ fn no_dangle() -> String {
- 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
- 引用必须总是有效的
## 课后练习
> [Rust By Practice](https://zh.practice.rs/ownership/borrowing.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。
> [Rust By Practice](https://zh.practice.rs/ownership/borrowing.html),支持代码在线编辑和运行,并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。

@ -100,7 +100,7 @@ let s = "hello"
#### 简单介绍 String 类型
之前提到过,本章会用 `String` 作为例子,因此这里会进行一下简单的介绍,具体的 `String` 学习请参见 [String 类型](../compound-type/string-slice.md)。
之前提到过,本章会用 `String` 作为例子,因此这里会进行一下简单的介绍,具体的 `String` 学习请参见 [String 类型](https://course.rs/basic/compound-type/string-slice.html)。
我们已经见过字符串字面值 `let s ="hello"``s` 是被硬编码进程序里的字符串值(类型为 `&str` )。字符串字面值是很方便的,但是它并不适用于所有场景。原因有二:
@ -262,7 +262,7 @@ Rust 有一个叫做 `Copy` 的特征,可以用在类似整型这样在栈中
- 所有浮点数类型,比如 `f64`
- 字符类型,`char`。
- 元组,当且仅当其包含的类型也都是 `Copy` 的时候。比如,`(i32, i32)` 是 `Copy` 的,但 `(i32, String)` 就不是。
- 引用类型,例如[转移所有权](#转移所有权)中的最后一个例子
- 不可变引用 `&T` ,例如[转移所有权](#转移所有权)中的最后一个例子**但是注意: 可变引用 `&mut T` 是不可以 Copy的**
## 函数传值与返回

@ -95,7 +95,7 @@ note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose bac
其中,默认的方式就是 `栈展开`,这意味着 Rust 会回溯栈上数据和函数调用,因此也意味着更多的善后工作,好处是可以给出充分的报错信息和栈调用信息,便于事后的问题复盘。`直接终止`,顾名思义,不清理数据就直接退出程序,善后工作交与操作系统来负责。
对于绝大多数用户,使用默认选择是最好的,但是当你关心最终编译出的二进制可执行文件大小时,那么可以尝试去使用直接终止的方式,例如下面的配置修改 `Cargo.toml` 文件,实现在 [`release`](../first-try/cargo.md#手动编译和运行项目) 模式下遇到 `panic` 直接终止:
对于绝大多数用户,使用默认选择是最好的,但是当你关心最终编译出的二进制可执行文件大小时,那么可以尝试去使用直接终止的方式,例如下面的配置修改 `Cargo.toml` 文件,实现在 [`release`](https://course.rs/first-try/cargo.html#手动编译和运行项目) 模式下遇到 `panic` 直接终止:
```rust
[profile.release]
@ -172,7 +172,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
当调用 `panic!` 宏时,它会
1. 格式化 `panic` 信息,然后使用该信息作为参数,调用 `std::panic::panic_any()` 函数
2. `panic_any` 会检查应用是否使用了 `panic hook`,如果使用了,该 `hook` 函数就会被调用(`hook` 是一个钩子函数,是外部代码设置的,用于在 `panic` 触发时,执行外部代码所需的功能)
2. `panic_any` 会检查应用是否使用了 [`panic hook`](https://doc.rust-lang.org/std/panic/fn.set_hook.html),如果使用了,该 `hook` 函数就会被调用(`hook` 是一个钩子函数,是外部代码设置的,用于在 `panic` 触发时,执行外部代码所需的功能)
3. 当 `hook` 函数返回后,当前的线程就开始进行栈展开:从 `panic_any` 开始,如果寄存器或者栈因为某些原因信息错乱了,那很可能该展开会发生异常,最终线程会直接停止,展开也无法继续进行
4. 展开的过程是一帧一帧的去回溯整个栈,每个帧的数据都会随之被丢弃,但是在展开过程中,你可能会遇到被用户标记为 `catching` 的帧(通过 `std::panic::catch_unwind()` 函数标记),此时用户提供的 `catch` 函数会被调用,展开也随之停止:当然,如果 `catch` 选择在内部调用 `std::panic::resume_unwind()` 函数,则展开还会继续。

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

@ -20,7 +20,7 @@ pub trait Iterator {
同时,`next` 方法也返回了一个 `Item` 类型,不过使用 `Option` 枚举进行了包裹,假如迭代器中的值是 `i32` 类型,那么调用 `next` 方法就将获取一个 `Option<i32>` 的值。
还记得 `Self` 吧?在之前的章节[提到过](https://course.rs/basic/trait/trait-object#self与self) `Self` 用来指代当前的特征实例,那么 `Self::Item` 就用来指代特征实例中具体的 `Item` 类型
还记得 `Self` 吧?在之前的章节[提到过](https://course.rs/basic/trait/trait-object#self-与-self) **`Self` 用来指代当前调用者的具体类型,那么 `Self::Item` 就用来指代该类型实现中定义的 `Item` 类型**
```rust
impl Iterator for Counter {
@ -30,9 +30,14 @@ impl Iterator for Counter {
// --snip--
}
}
fn main() {
let c = Counter{..}
c.next()
}
```
在上述代码中,我们为 `Counter` 类型实现了 `Iterator` 特征,那么 `Self` 就是当前的 `Iterator` 特征对象, `Item` 就是 `u32` 类型。
在上述代码中,我们为 `Counter` 类型实现了 `Iterator` 特征,变量 `c` 是特征 `Iterator` 的实例,也是 `next` 方法的调用者。 结合之前的黑体内容可以得出:对于 `next` 方法而言,`Self` 是调用者 `c` 的具体类型: `Counter`,而 `Self::Item``Counter` 中定义的 `Item` 类型: `u32`
聪明的读者之所以聪明,是因为你们喜欢联想和举一反三,同时你们也喜欢提问:为何不用泛型,例如如下代码:
@ -227,7 +232,7 @@ Up!
因为 `fly` 方法的参数是 `self`,当显式调用时,编译器就可以根据调用的类型( `self` 的类型)决定具体调用哪个方法。
这个时候问题又来了,如果方法没有 `self` 参数呢?稍等,估计有读者会问:还有方法没有 `self` 参数?看到这个疑问,作者的眼泪不禁流了下来,大明湖畔的[关联函数](../method.md#关联函数),你还记得嘛?
这个时候问题又来了,如果方法没有 `self` 参数呢?稍等,估计有读者会问:还有方法没有 `self` 参数?看到这个疑问,作者的眼泪不禁流了下来,大明湖畔的[关联函数](https://course.rs/basic/method.html#关联函数),你还记得嘛?
但是成年人的世界,就算再伤心,事还得做,咱们继续:

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save