Merge pull request #354 from JesseAtSZ/patch-11

Update drop.md
pull/358/head
Sunface 3 years ago committed by GitHub
commit b4ea9f5422
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,17 +1,17 @@
# Drop释放资源
在Rust中我们之所以可以一拳打跑GC的同时一脚踢翻手动资源回收主要就归功于`Drop`特征,同时它也是智能指针的必备特征之一。
# Drop 释放资源
Rust 中,我们之所以可以一拳打跑 GC 的同时一脚踢翻手动资源回收,主要就归功于 `Drop` 特征,同时它也是智能指针的必备特征之一。
## 学习目标
如何自动和手动释放资源及执行指定的收尾工作
## Rust中的资源回收
在一些无GC语言中程序员在一个变量无需再被使用时需要手动释放它占用的内存资源如果忘记了那么就会发生内存泄漏最终臭名昭著的`OOM`问题可能就会发生。
## Rust 中的资源回收
在一些无 GC 语言中,程序员在一个变量无需再被使用时,需要手动释放它占用的内存资源,如果忘记了,那么就会发生内存泄漏,最终臭名昭著的 `OOM` 问题可能就会发生。
而在Rust中你可以指定在一个变量超出作用域时执行一段特定的代码最终编译器将帮你自动插入这段收尾代码。这样就无需在每一个使用该变量的地方都写一段代码来进行收尾工作和资源释放。不禁让人感叹Rust的大腿真粗
而在 Rust 你可以指定在一个变量超出作用域时执行一段特定的代码最终编译器将帮你自动插入这段收尾代码。这样就无需在每一个使用该变量的地方都写一段代码来进行收尾工作和资源释放。不禁让人感叹Rust 的大腿真粗,香!
没错,指定这样一段收尾工作靠的就是咱这章的主角 - `Drop`特征。
没错,指定这样一段收尾工作靠的就是咱这章的主角 - `Drop` 特征。
## 一个不那么简单的Drop例子
## 一个不那么简单的 Drop 例子
```rust
struct HasDrop1;
struct HasDrop2;
@ -43,20 +43,22 @@ impl Drop for Foo {
}
}
fn main() {
let _x = HasTwoDrops { two: HasDrop2 ,one: HasDrop1,};
let _x = HasTwoDrops {
two: HasDrop2,
one: HasDrop1,
};
let _foo = Foo;
println!("Running!");
}
```
上面代码虽然长,但是目的其实很单纯,就是为了观察不同情况下的`Drop`变量级别的、结构体内部字段的, 有几点值得注意:
上面代码虽然长,但是目的其实很单纯,就是为了观察不同情况下变量级别的、结构体内部字段的 `Drop`有几点值得注意:
- `Drop`特征中的`drop`方法借用了目标的可变引用,而不是拿走了所有权,这里先设置一个悬念,后边会讲
- 结构体中每个字段都有自己的`Drop`
- `Drop` 特征中的 `drop` 方法借用了目标的可变引用,而不是拿走了所有权,这里先设置一个悬念,后边会讲
- 结构体中每个字段都有自己的 `Drop`
来看看输出:
来看看输出
```console
Running!
Dropping Foo!
@ -65,16 +67,16 @@ Dropping HasDrop1!
Dropping HasDrop2!
```
嗯,结果符合预期,每个资源都成功的执行了收尾工作,虽然`println!`这种收尾工作毫无意义 = , =
嗯,结果符合预期,每个资源都成功的执行了收尾工作,虽然 `println!` 这种收尾工作毫无意义 =,=
#### Drop的顺序
观察以上输出,我们可以得出以下关于`Drop`顺序的结论
#### Drop 的顺序
观察以上输出,我们可以得出以下关于 `Drop` 顺序的结论
- **变量级别,按照逆序的方式**,`_x`在`_foo`之前创建,因此`_x`在`_foo`之后被drop
- **结构体内部,按照顺序的方式**, 结构体`_x`中的字段按照定义中的顺序依次`drop`
- **变量级别,按照逆序的方式**`_x` 在 `_foo` 之前创建,因此 `_x``_foo` 之后被 `drop`
- **结构体内部,按照顺序的方式**,结构体 `_x` 中的字段按照定义中的顺序依次 `drop`
#### 没有实现Drop的结构体
实际上,就算你不为`_x`结构体实现`Drop`特征,它内部的两个字段依然会调用`drop`,移除以下代码,并观察输出:
#### 没有实现 Drop 的结构体
实际上,就算你不为 `_x` 结构体实现 `Drop` 特征,它内部的两个字段依然会调用 `drop`,移除以下代码,并观察输出:
```rust
impl Drop for HasTwoDrops {
fn drop(&mut self) {
@ -83,15 +85,15 @@ impl Drop for HasTwoDrops {
}
```
原因在于Rust自动为几乎所有类型都实现了`Drop`特征,因此就算你不手动为结构体实现`Drop`,它依然会调用默认实现的`drop`函数,同时再调用每个字段的`drop`方法,最终打印出:
原因在于Rust 自动为几乎所有类型都实现了 `Drop` 特征,因此就算你不手动为结构体实现 `Drop`,它依然会调用默认实现的 `drop` 函数,同时再调用每个字段的 `drop` 方法,最终打印出:
```cnosole
Dropping HasDrop1!
Dropping HasDrop2!
```
## 手动回收
当使用智能指针来管理锁的时候,你可能希望提前释放这个锁,然后让其它代码能及时获得锁,此时就需要提前去手动`drop`。
但是在之前我们提到一个悬念,就是`Drop::drop`只是借用了目标值的可变引用,就算你提前调用了该方法,但是后面的代码依然可以使用目标值,这就会访问一个并不存在的值非常不安全好在Rust会阻止你
当使用智能指针来管理锁的时候,你可能希望提前释放这个锁,然后让其它代码能及时获得锁,此时就需要提前去手动 `drop`
但是在之前我们提到一个悬念,`Drop::drop` 只是借用了目标值的可变引用,所以,就算你提前调用了 `drop`,后面的代码依然可以使用目标值,但是这就会访问一个并不存在的值,非常不安全,好在 Rust 会阻止你:
```rust
#[derive(Debug)]
struct Foo;
@ -121,15 +123,15 @@ error[E0040]: explicit use of destructor method
| help: consider using `drop` function: `drop(foo)`
```
如上所示,编译器直接阻止了我们调用`Drop`特征的`drop`方法原因是该方法是析构函数这是一个用来清理实例的通用编程概念对于Rust而言不允许显式的调用析构函数。好在在报错的同时,编译器还给出了一个提示:使用`drop`函数。
如上所示,编译器直接阻止了我们调用 `Drop` 特征的 `drop` 方法,原因是对于 Rust 而言,不允许显式地调用析构函数(这是一个用来清理实例的通用编程概念)。好在在报错的同时,编译器还给出了一个提示:使用 `drop` 函数。
针对编译器提示的`drop`函数,我们可以大胆推测下:它能够拿走目标值的所有权。现在来看看这个猜测正确与否,以下是`std::mem::drop`函数的签名:
针对编译器提示的 `drop` 函数,我们可以大胆推测下:它能够拿走目标值的所有权。现在来看看这个猜测正确与否,以下是 `std::mem::drop` 函数的签名:
```rust
pub fn drop<T>(_x: T)
```
如上所示,`drop`函数确实拿走了目标值的所有权,来验证下:
如上所示,`drop` 函数确实拿走了目标值的所有权,来验证下:
```rust
fn main() {
let foo = Foo;
@ -141,22 +143,22 @@ fn main() {
Bingo完美拿走了所有权而且这种实现保证了后续的使用必定会导致编译错误因此非常安全
细心的同学可能已经注意到,这里直接调用了`drop`函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](../../appendix/prelude.md)里。
细心的同学可能已经注意到,这里直接调用了 `drop` 函数,并没有引入任何模块信息,原因是该函数在[`std::prelude`](../../appendix/prelude.md)里。
## Drop使用场景
对于Drop而言主要有两个功能
## Drop 使用场景
对于 Drop 而言,主要有两个功能:
- 回收内存资源
- 执行一些收尾工作
对于第二点,在之前我们已经详细介绍过,因此这里主要对第一点进行下简单说明。
在绝大多数情况下,我们都无需手动去`drop`以回收内存资源因为Rust会自动帮我们完成这些工作它甚至会对复杂类型的每个字段都单独的调用`drop`进行回收但是确实有极少数情况需要你自己来回收资源的例如文件描述符、网络socket等当这些值超出作用域不再使用时就需要进行关闭以释放相关的资源在这些情况下就需要使用者自己来解决`Drop`的问题。
在绝大多数情况下,我们都无需手动去 `drop` 以回收内存资源,因为 Rust 会自动帮我们完成这些工作,它甚至会对复杂类型的每个字段都单独的调用 `drop` 进行回收!但是确实有极少数情况,需要你自己来回收资源的,例如文件描述符、网络 socket 等,当这些值超出作用域不再使用时,就需要进行关闭以释放相关的资源,在这些情况下,就需要使用者自己来解决 `Drop` 的问题。
## 互斥的Copy和Drop
我们无法为一个类型同时实现`Copy`和`Drop`特征。因为实现了`Copy`的特征会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了`Copy`的类型无法拥有析构函数。
## 互斥的 Copy Drop
我们无法为一个类型同时实现 `Copy` `Drop` 特征。因为实现了 `Copy` 的特征会被编译器隐式的复制,因此非常难以预测析构函数执行的时间和频率。因此这些实现了 `Copy` 的类型无法拥有析构函数。
```rust
#[derive(Copy)]
struct Foo;
@ -167,7 +169,7 @@ impl Drop for Foo {
}
}
```
下代码报错如下:
上代码报错如下:
```console
error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor
--> src/main.rs:24:10
@ -178,6 +180,6 @@ error[E0184]: the trait `Copy` may not be implemented for this type; the type ha
## 总结
`Drop`可以用于许多方面,来使得资源清理及收尾工作变得方便和安全,甚至可以用其创建我们自己的内存分配器!通过`Drop`特征和 Rust 所有权系统你无需担心之后的代码清理Rust 会自动考虑这些问题。
`Drop` 可以用于许多方面,来使得资源清理及收尾工作变得方便和安全,甚至可以用其创建我们自己的内存分配器!通过 `Drop` 特征和 Rust 所有权系统你无需担心之后的代码清理Rust 会自动考虑这些问题。
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保`drop`只会在值不再被使用时被调用一次。
我们也无需担心意外的清理掉仍在使用的值,这会造成编译器错误:所有权系统确保引用总是有效的,也会确保 `drop` 只会在值不再被使用时被调用一次。

Loading…
Cancel
Save