mirror of
https://github.com/RustyCab/LearnRustEasy.git
synced 2026-02-03 18:23:31 +08:00
update dir for pitcure
This commit is contained in:
@@ -157,7 +157,7 @@ cargo new hello_world
|
||||
|
||||
创建后,可以看到整个目录的结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
其中Cargo.toml是项目的配置文件,src为源代码目录,main.rs为主程序文件。
|
||||
|
||||
@@ -184,7 +184,7 @@ cargo run
|
||||
|
||||
运行结果如下:
|
||||
|
||||

|
||||

|
||||
|
||||
### 2.3.4 理解Rust源码的基本结构
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ fn main() {
|
||||
|
||||
在上面的代码中,vehicle1和vehicle1都是Vehicle trait对象的引用;对于vehicle1来说,trait对象的具体类型是`Car`;对于vehicle2来说,trait对象的具体类型是`Truck`。上面代码对应的内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
变量car和变量truck分别是Car类型和Truck类型,存储在栈上;vehicle1和vehicle2是Vehicle trait对象的引用,具有两个指针,其中指针ptr指向具体类型的实例,vptr指向虚函数表;虚函数表存储在只读数据区。
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ fn main() {
|
||||
|
||||
上面代码中,x的有效作用域是从第4行到第6行花括号结束前,即从第6行的花括号后开始,x变为无效,r为x的应用,此时将指向的是一块无效的内存。其内存示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
## 3.11.2 借用检查
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ fn main() {
|
||||
|
||||
运行该程序会打印如下错误:
|
||||
|
||||

|
||||

|
||||
|
||||
运行时添加```RUST_BACKTRACE=1```,可以打印完整的堆栈,上面的代码运行时加```RUST_BACKTRACE=1```的结果如下:
|
||||
|
||||

|
||||

|
||||
|
||||
## 3.12.2 用Result处理可恢复错误
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
指针是一个包含了内存地址的变量,该内存地址引用或者指向了另外的数据,其在内存中的示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
智能指针是一类数据结构,其表现类似于指针,但是相对于指针来说,还拥有额外的元数据。最明显的是它们拥有一个引用计数。引用计数记录了智能指针总共有多少个所有者,并且当没有任何所有者时清除数据。普通引用和智能指针的另一个**非常重要的区别**就是:**引用只是只借用数据的指针,而智能指针则是拥有它们指向的数据**。
|
||||
|
||||
**智能指针是一个胖指针,但是胖指针不一定是智能指针**。前面章节介绍过的```String```类型就是一个智能指针,而它对应的切片引用```&str```则只仅仅是一个胖指针,区别就在于```String```类型拥有对数据的所有权,而```&str```没有。两者在内存中的示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
同样的,**Vec类型也是一个智能指针**。
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ fn main() {
|
||||
|
||||
前面示例中使用```Box```定义了变量```b```,其内存布局方式如下:
|
||||
|
||||

|
||||

|
||||
|
||||
## 3. Box适合使用的场景
|
||||
|
||||
@@ -45,7 +45,7 @@ fn main() {
|
||||
```
|
||||
但是上面的代码无法编译通过,因为```Cons```类型在编译时无法确定其具体大小。其内存示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
此时就需要使用Box,其代码如下:
|
||||
```Rust
|
||||
@@ -65,7 +65,7 @@ fn main() {
|
||||
```
|
||||
使用Box后,上面的Cons的内存表示如下:
|
||||
|
||||

|
||||

|
||||
|
||||
每个Box的大小是固定的,所以编译不会有问题。
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ fn main() {
|
||||
|
||||
运行该代码,会有如下结果:
|
||||
|
||||

|
||||

|
||||
|
||||
在上面示例代码中并没有打印语句,但是在drop方法中实现了打印,可以看到,当_a和_b离开作用域时,自动调用了drop方法。
|
||||
|
||||
@@ -53,6 +53,6 @@ fn main() {
|
||||
|
||||
代码运行结果如下:
|
||||
|
||||

|
||||

|
||||
|
||||
第一个“Dog leave”打印是第14行调用释放_a产生,最后一个“Dog leave”打印则是_b离开作用域时调用drop方法产生。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
假定有这样一个需求,希望创建两个共享第三个列表所有权的列表,其概念类似于如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
根据前面的知识,可能写出来的代码如下:
|
||||
|
||||
@@ -39,7 +39,7 @@ fn main() {
|
||||
上面代码中,a、b、c就共享数据5。当创建b时,不会获取a的所有权,会克隆a所包含的Rc(5u32),这会使引用计数从1增加到2并允许a和b共享Rc(5u32)的所有权。
|
||||
创建c时也会克隆 a,引用计数从2增加为3。每次调用`Rc::clone`,Rc(5u32)的引用计数都会增加,直到有零个引用之前其数据都不会被清理。其在内存中的表示如下:
|
||||
|
||||

|
||||

|
||||
|
||||
前面共享列表的需求则可以使用Rc实现如下:
|
||||
```Rust
|
||||
@@ -58,7 +58,7 @@ fn main() {
|
||||
|
||||
对应的内存示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## 1. 引用循环和内存泄露
|
||||
|
||||
下图是一个循环链表:
|
||||

|
||||

|
||||
|
||||
下面的代码试图用之前学到的智能指针相关知识实现上面的链表:
|
||||
|
||||
@@ -55,23 +55,23 @@ fn main() {
|
||||
|
||||
- 当执行完第26行后,内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
- 在执行完第33行后,b对应的Rc引用计数变成2,内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
此时如果第40行代码执行将会panic,因为已经成了一个循环链表,Rust无法匹配到a的tail,最终会造成栈溢出。
|
||||
|
||||
- 在最后离开作用域时,Rust将会对b和a调用drop方法,对b调用drop方法后,内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
此时b的Rc实例引用计数减去1,但是仍然不为0(因为第33行让a也引用了b的Rc实例),所以b所指向的内存并不会被释放。
|
||||
|
||||
- 然后Rust尝试drop a,其对应的Rc示例引用计数减去1,但仍然不为0,所以调用a的drop后,内存布局为:
|
||||
|
||||

|
||||

|
||||
|
||||
至此,造成内存泄露。
|
||||
|
||||
@@ -162,7 +162,7 @@ fn main() {
|
||||
|
||||
下图为上面代码的内存布局示意图:
|
||||
|
||||

|
||||

|
||||
|
||||
下面再总结一下Weak的特点:
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ cargo new main
|
||||
|
||||
进入main文件夹,其目录层级如下:
|
||||
|
||||

|
||||

|
||||
|
||||
其中Cargo.toml的内容如下:
|
||||
|
||||

|
||||

|
||||
|
||||
可以看到这个crate的名字为main。
|
||||
|
||||
@@ -50,6 +50,6 @@ members = [
|
||||
|
||||
最后的目录层级关系如下:
|
||||
|
||||

|
||||

|
||||
|
||||
上面的步骤就创建了一个包my-pack,这个包包含两个crate,分别是my-lib和main。
|
||||
|
||||
@@ -160,4 +160,4 @@ fn main() {
|
||||
|
||||
整个工程的目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -28,7 +28,7 @@ cd main
|
||||
|
||||
打开main目录下的Cargo.toml文件,在[dependencies]下添加对rand库的依赖(即添加语句rand = "0.8.5"),添加后整个文件的内容如下:
|
||||
|
||||

|
||||

|
||||
|
||||
### (3)在代码中使用rand库
|
||||
编写src/main.rs如下:
|
||||
|
||||
@@ -68,6 +68,6 @@ fn main() {
|
||||
|
||||
在上面的示例中,通过my-project/Cargo.toml文件管理了main和add两个crate,上面所有步骤完成后,整个项目构成如下:
|
||||
|
||||

|
||||

|
||||
|
||||
在my-project目录下或者my-project/main目录下运行cargo run可以编译执行整个项目。
|
||||
|
||||
@@ -41,7 +41,7 @@ cargo test
|
||||
```
|
||||
|
||||
运行后可以看到对应的测试结果:
|
||||

|
||||

|
||||
|
||||
## 2. 使用断言
|
||||
在前面的示例中,测试代码中使用了assert_eq!断言。除了assert_eq!以外,下面几个也是比较常用的断言:
|
||||
|
||||
@@ -73,7 +73,7 @@ cargo test add_two_and_two
|
||||
|
||||
执行结果如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
## 4. 过滤运行测试
|
||||
|
||||
@@ -84,7 +84,7 @@ cargo test add
|
||||
|
||||
执行结果如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
## 5. 忽略某个测试
|
||||
有时候运行cargo test时想忽略其中的某个测试,此时可以通过使用ignore属性来标记该测试来排除它。例如有如下测试代码:
|
||||
@@ -116,4 +116,4 @@ mod tests {
|
||||
```
|
||||
上面的代码中将add_two_and_two用#[ignore]忽略,运行cargo test将不会执行该函数,执行结果如下:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -13,7 +13,7 @@ pub mod sub;
|
||||
|
||||
运行```cargo doc --open```出现如下界面:
|
||||
|
||||

|
||||

|
||||
|
||||
而下面的示例因为没有将所有的包注释放在文件最上面,所以会报错:
|
||||
```Rust
|
||||
@@ -74,13 +74,13 @@ pub fn add2(left: u32, right: u32) -> Option<u32> {
|
||||
|
||||
运行cargo test,可以看到运行了用例add::add2,如下:
|
||||
|
||||

|
||||

|
||||
|
||||
运行cargo doc --open后,打开add和add2的文档,分别如下:
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
可以看到add2对应的测试用例的内容在文档中被隐藏了。
|
||||
@@ -98,7 +98,7 @@ pub fn sub(left: u32, right: u32) -> Option<u32> {
|
||||
```
|
||||
运行cargo doc --open后,出现如下:
|
||||
|
||||

|
||||

|
||||
|
||||
从上图中,点击红色划线部分将分别跳转到标准库的Option和crate::add的位置。
|
||||
|
||||
@@ -112,4 +112,4 @@ pub struct A;
|
||||
|
||||
运行cargo doc --open后,出现如下:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -27,15 +27,15 @@ fn main() {
|
||||
|
||||
运行前整个项目的目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
运行cargo run执行程序结果如下:
|
||||
|
||||

|
||||

|
||||
|
||||
运行后整个项目的目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
- 构建脚本的生命周期
|
||||
@@ -84,7 +84,7 @@ cc = "1.0.46" # 可以在build.rs中使用cc相关的功能
|
||||
|
||||
下面的示例展示如何在Rust中使用c代码,整个项目的目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
c目录中的pass.c为c代码,源码如下:
|
||||
```Rust
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
下面示例展示在c中调用Rust,其目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
目录中的foo是Rust代码,用cargo new foo --lib创建,其中src/lib.rs的代码如下:
|
||||
@@ -53,4 +53,4 @@ int main()
|
||||
|
||||
在main.c同级目录运行命令```gcc -o main main.c ./foo/target/debug/libfoo.a -lpthread -ldl```会生成执行文件main,如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -24,7 +24,7 @@ struct A {
|
||||
|
||||
下面给出一个完整的自定义derive宏的示例,整个项目的目录如下:
|
||||
|
||||

|
||||

|
||||
|
||||
整个项目中有三个crate,功能分别如下:
|
||||
|
||||
@@ -138,7 +138,7 @@ fn main() {
|
||||
## 2. 类函数宏
|
||||
类函数宏是类似于函数那样的过程宏,下面的工程演示类函数宏,其目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
### (1)定义工作空间
|
||||
编写最外层的Cargo.toml文件,内容如下:
|
||||
@@ -195,7 +195,7 @@ fn main() {
|
||||
## 3. 类属性宏
|
||||
属性宏和自定义derive宏类似,不同的是derive宏生成代码,而类属性宏可以创建新的属性。自定义derive宏只能用于结构体和枚举,属性宏则还可以用于其它的项,如函数。下面的工程演示类属性宏,其目录结构如下:
|
||||
|
||||

|
||||

|
||||
|
||||
### (1)定义工作空间
|
||||
编写最外层的Cargo.toml文件,内容如下:
|
||||
|
||||
@@ -99,4 +99,4 @@ cargo doc --open
|
||||
|
||||
将打开上面代码里面文档注释生成的文档,如下图:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 3.6.1 Rust程序内存布局
|
||||
|
||||

|
||||

|
||||
|
||||
上图是一张linux系统上Rust程序的内存布局图。在linux操作系统中,会划分固定的区域给内核使用,即上图中的内核空间;应用程序使用的是用户空间。
|
||||
|
||||
@@ -44,7 +44,7 @@ fn main() {
|
||||
|
||||
对于上面的代码,在执行第17行和第18行的栈帧示意图如下:
|
||||
|
||||

|
||||

|
||||
|
||||
这里需要注意的是两个帧对应同样的内存地址,这是因为在调用完f1函数后,其对应的栈帧释放(释放的实际意义就是这段内存可以被重新分配了),然后调用f2函数为其分配栈帧时从同样的地址进行分配。
|
||||
|
||||
@@ -64,4 +64,4 @@ Rust没有GC,但通过其独特的机制管理内存,程序员不用手动
|
||||
|
||||
当数据需要存放到堆上时,内存分配器则是根据数据的大小,在堆内存找到合适大小的空区域存放,把它标记为已使用,并返回一个表示该位置地址的指针。该指针存储在栈上,当需要访问具体的数据时,必须先访问指针,然后通过指针找到堆上的位置,从而访问数据。这个过程可以用下图表示:
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -158,7 +158,7 @@ fn main() {
|
||||
|
||||
在第2行定义String类型时,并不能确定最终字符串的大小,所以字符串内容本身应该存储在堆上。结合什么String类型的本质的内容,可以得到String类型的存储如下:
|
||||
|
||||

|
||||

|
||||
|
||||
String类型本身是三个字段(指针、长度、容量),在编译时是已知的大小,存储在栈上;String类型绑定的字符串(在上面代码中是“AB”)在编译时大小未知,是运行时在堆上分配内存,分配后的内存地址保存在String类型的指针字段中,内存大小保存在cap字段中,内存上存储的字符串长度保存在len字段中。
|
||||
|
||||
@@ -193,11 +193,11 @@ fn main() {
|
||||
```
|
||||
s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
当执行let s1 = s后,内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
当`let s1 = s`执行后,就发生了所有权的转移,String类型值的所有权从`s`转移到了`s1`。此时Rust认为原来的`s`不再有效。因此,上面代码第4行打开编译将会出错。
|
||||
|
||||
@@ -208,14 +208,14 @@ s是`String`类型,字符串`"Hello world"`是存储在堆内存上的,其
|
||||
只拷贝栈上的内容,就叫做浅拷贝。
|
||||
对于上面的String类型,执行`let s1 = s`后,只把`s`的`ptr`、`len`、`cap`中的值拷贝给`s1`的`ptr`、`len`、`cap`的值,这种就叫做浅拷贝。浅拷贝发生后,`s`的`ptr`和`s1`的`ptr`都指向同样的堆内存。内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
### 5.2 深拷贝
|
||||
|
||||
除了拷贝栈上的内容外,还拷贝堆内存中的内容,就叫做深拷贝。
|
||||
对于上面的String类型,执行`let s1 = s`后,除了把`s`的`len`、`cap`中的值拷贝给`s1`的`len`、`cap`外,还在堆上重新分配一块内存,将s的ptr指向的堆内存的内容拷贝到这块内存,然后`s1`的`ptr`指向这块内存,这种拷贝就叫做深拷贝。深拷贝发生后,`s`的`ptr`和`s1`的`ptr`指向不同的堆内存,但是堆内存中存储的内容一样。深拷贝发生后的内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
显然,**Rust中变量赋值(Rust中叫所有权转移)使用的是浅拷贝**。
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ fn print(s: String) -> String {
|
||||
|
||||
上面代码中,变量`a`、`b`、`c`、`d`的内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
**获取变量的引用,称之为借用** 。通过借用,允许使用被引用变量绑定的值,同时又没有移动该变量的所有权。前面的示例代码可以变成如下:
|
||||
```Rust
|
||||
@@ -265,11 +265,11 @@ int main()
|
||||
|
||||
在执行第14行前,其内存布局为:
|
||||
|
||||

|
||||

|
||||
|
||||
当执行第14行后,变成如下:
|
||||
|
||||

|
||||

|
||||
|
||||
第14行执行后,`ptr`就变成了一个悬垂指针(或者交悬垂引用),然后在第16行继续使用`ptr`,则会发生错误。
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ fn main() {
|
||||
|
||||
`&str`和`&String`的内存布局如下:
|
||||
|
||||

|
||||

|
||||
|
||||
## 3. 其它Slice
|
||||
|
||||
|
||||
Reference in New Issue
Block a user