diff --git a/course-book/contents/SUMMARY.md b/course-book/contents/SUMMARY.md index b0c18399..5ddd9156 100644 --- a/course-book/contents/SUMMARY.md +++ b/course-book/contents/SUMMARY.md @@ -67,16 +67,17 @@ - [智能指针 doing](advance/smart-pointer/intro.md) - [Box堆对象分配](advance/smart-pointer/box.md) - [Deref解引用](advance/smart-pointer/deref.md) - - [Cell todo](advance/smart-pointer/cell.md) - - [Rc与RefCell(todo)](advance/smart-pointer/rc-refcell.md) - - [自引用与内存泄漏(todo)](advance/smart-pointer/self-referrence.md) + - [Drop释放资源](advance/smart-pointer/drop.md) + - [Cell与RefCell todo](advance/smart-pointer/cell.md) + - [Rc与Arc todo](advance/smart-pointer/rc-refcell.md) + - [自引用与内存泄漏 todo](advance/smart-pointer/self-referrence.md) - [全局变量 todo](advance/global-variable.md) - [多线程 todo](advance/multi-threads/intro.md) - - [线程管理(todo)](advance/multi-threads/thread.md) - - [消息传递(todo)](advance/multi-threads/message-passing.md) - - [数据共享Arc、Mutex、Rwlock(todo)](advance/multi-threads/ref-counter-lock.md) - - [数据竞争(todo)](advance/multi-threads/races.md) - - [Send、Sync(todo)](advance/multi-threads/send-sync.md) + - [线程管理 todo](advance/multi-threads/thread.md) + - [消息传递 todo](advance/multi-threads/message-passing.md) + - [数据共享Mutex、Rwlock todo](advance/multi-threads/ref-counter-lock.md) + - [数据竞争 todo](advance/multi-threads/races.md) + - [Send、Sync todo](advance/multi-threads/send-sync.md) ## 专题内容,每个专题都配套一个小型项目进行实践 - [Rust最佳实践 doing](practice/intro.md) diff --git a/course-book/contents/advance/smart-pointer/deref.md b/course-book/contents/advance/smart-pointer/deref.md index 9ac9f9b8..fd842767 100644 --- a/course-book/contents/advance/smart-pointer/deref.md +++ b/course-book/contents/advance/smart-pointer/deref.md @@ -1,5 +1,5 @@ # Deref解引用 -智能指针的名称来源,主要就在于它实现了`Deref`和`Drop`特征,这两个特征可以智能地帮助我们节省使用上的负担: +何为智能指针?能不让你写出&&&&&&s形式的解引用,我认为就是智能: ) 智能指针的名称来源,主要就在于它实现了`Deref`和`Drop`特征,这两个特征可以智能地帮助我们节省使用上的负担: - `Deref`可以让智能指针像引用那样工作,这样你就就可以写出同时支持智能指针和引用的代码, 例如`&T` - `Drop`允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作 diff --git a/course-book/contents/advance/smart-pointer/drop.md b/course-book/contents/advance/smart-pointer/drop.md new file mode 100644 index 00000000..d731a460 --- /dev/null +++ b/course-book/contents/advance/smart-pointer/drop.md @@ -0,0 +1,183 @@ +# Drop释放资源 +在Rust中,我们之所以可以一拳打跑GC的同时一脚踢翻手动资源回收,主要就归功于`Drop`特征,同时它也是智能指针的必备特征之一。 + +## 学习目标 +如何自动和手动释放资源及执行指定的收尾工作 + +## Rust中的资源回收 +在一些无GC语言中,程序员在一个变量无需再被使用时,需要手动释放它占用的内存资源,如果忘记了,那么就会发生内存泄漏,最终臭名昭著的`OOM`问题可能就会发生。 + +而在Rust中,你可以指定在一个变量超出作用域时,执行一段特定的代码,最终编译器将帮你自动插入这段收尾代码。这样,就无需在每一个使用该变量的地方,都写一段代码来进行收尾工作和资源释放。不仅让人感叹,Rust的大腿真粗,香! + +没错,指定这样一段收尾工作靠的就是咱这章的主角 - `Drop`特征。 + +## 一个不那么简单的Drop例子 +```rust +struct HasDrop1; +struct HasDrop2; +impl Drop for HasDrop1 { + fn drop(&mut self) { + println!("Dropping HasDrop1!"); + } +} +impl Drop for HasDrop2 { + fn drop(&mut self) { + println!("Dropping HasDrop2!"); + } +} +struct HasTwoDrops { + one: HasDrop1, + two: HasDrop2, +} +impl Drop for HasTwoDrops { + fn drop(&mut self) { + println!("Dropping HasTwoDrops!"); + } +} + +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} + + +fn main() { + let _x = HasTwoDrops { two: HasDrop2 ,one: HasDrop1,}; + let _foo = Foo; + println!("Running!"); +} +``` + +上面代码虽然长,但是目的其实很单纯,就是为了观察不同情况下的`Drop`,变量级别的、结构体内部字段的, 有几点值得注意: + +- `Drop`特征中的`drop`方法借用了目标的可变引用,而不是拿走了所有权,这里先设置一个悬念,后边会讲 +- 结构体中每个字段都有自己的`Drop` + +来看看输出: +```console +Running! +Dropping Foo! +Dropping HasTwoDrops! +Dropping HasDrop1! +Dropping HasDrop2! +``` + +嗯,结果符合预期,每个资源都成功的执行了收尾工作,虽然`println!`这种收尾工作毫无意义 = , = + +#### Drop的顺序 +观察以上输出,我们可以得出以下关于`Drop`顺序的结论 + +- **变量级别,按照逆序的方式**,,`_x`在`_foo`之前创建,因此`_x`在`_foo`之后被drop +- **结构体内部,按照顺序的方式**, 结构体`_x`中的字段按照定义中的顺序依次`drop` + +#### 没有实现Drop的结构体 +实际上,就算你不为`_x`结构体实现`Drop`特征,它内部的两个字段依然会调用`drop`,移除以下代码,并观察输出: +```rust +impl Drop for HasTwoDrops { + fn drop(&mut self) { + println!("Dropping HasTwoDrops!"); + } +} +``` + +原因在于,Rust自动为几乎所有类型都实现了`Drop`特征,因此就算你不手动为结构体实现`Drop`,它依然会调用默认实现的`drop`函数,同时再调用每个字段的`drop`方法,最终打印出: +```cnosole +Dropping HasDrop1! +Dropping HasDrop2! +``` + +## 手动回收 +当使用智能指针来管理锁的时候,你可能希望提前释放这个锁,然后让其它代码能及时获得锁,此时就需要提前去手动`drop`。 +但是在之前我们提到一个悬念,就是`Drop::drop`只是借用了目标值的可变引用,就算你提前调用了该方法,但是后面的代码依然可以使用目标值,这就会访问一个并不存在的值,非常不安全,好在Rust会阻止你: +```rust +#[derive(Debug)] +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} + +fn main() { + let foo = Foo; + foo.drop(); + println!("Running!:{:?}", foo); +} +``` + +报错如下: +```console +error[E0040]: explicit use of destructor method + --> src/main.rs:37:9 + | +37 | foo.drop(); + | ----^^^^-- + | | | + | | explicit destructor calls not allowed + | help: consider using `drop` function: `drop(foo)` +``` + +如上所示,编译器直接阻止了我们调用`Drop`特征的`drop`方法,原因是该方法是析构函数,这是一个用来清理实例的通用编程概念,对于Rust而言,不允许显式的调用析构函数。好在在报错的同时,编译器还给出了一个提示:使用`drop`函数。 + + +针对编译器提示的`drop`函数,我们可以大胆推测下:它能够拿走目标值的所有权。现在来看看这个猜测正确与否,以下是`std::mem::drop`函数的签名: +```rust +pub fn drop(_x: T) +``` + +如上所示,`drop`函数确实拿走了目标值的所有权,来验证下: +```rust +fn main() { + let foo = Foo; + drop(foo); + // 以下代码会报错:借用了所有权被转移的值 + // println!("Running!:{:?}", foo); +} +``` + +Bingo,完美拿走了所有权,而且这种实现保证了后续的使用必定会导致编译错误,因此非常安全! + +细心的同学可能已经注意到,这里直接调用了`drop`函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](../../appendix/prelude.md)里。 + + +## Drop使用场景 +对于Drop而言,主要有两个功能: + +- 回收内存资源 +- 执行一些收尾工作 + +对于第二点,在之前我们已经详细介绍过,因此这里主要对第一点进行下简单说明。 + +在绝大多数情况下,我们都无需手动去`drop`以回收内存资源,因为Rust会自动帮我们完成这些工作,它甚至会对复杂类型的每个字段都单独的调用`drop`进行回收!但是确实有极少数情况,需要你自己来回收资源的,例如文件描述符、网络socket等,当这些超出作用域不再使用时,就需要进行关闭以释放相关的资源,在这些情况下,就需要使用者自己来解决`Drop`的问题。 + + +## 互斥的Copy和Drop +我们无法为一个类型同时实现`Copy`和`Drop`特征。因为实现了`Copy`的特征会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了`Copy`的类型无法拥有析构函数。 +```rust +#[derive(Copy)] +struct Foo; + +impl Drop for Foo { + fn drop(&mut self) { + println!("Dropping Foo!") + } +} +``` +以下代码报错如下: +```console +error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor + --> src/main.rs:24:10 + | +24 | #[derive(Copy)] + | ^^^^ Copy not allowed on types with destructors +``` + + +## 总结 +`Drop`可以用于许多方面,来使得资源清理及收尾工作变得方便和安全,甚至可以用其创建我们自己的内存分配器!通过`Drop`特征和 Rust 所有权系统,你无需担心之后的代码清理,Rust 会自动考虑这些问题。 + +我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保`drop`只会在值不再被使用时被调用一次。 \ No newline at end of file