15.4 Drop trait:告别手动清理,释放即安全
15.4.1 Drop trait 的意义
如果某个类型实现了 Drop trait,就可以让程序员自定义值离开作用域时发生的操作。例如释放文件或网络资源。
在某些语言中(比如 C/C++),对于某些类型,程序员每次用完这些类型的实例时都必须写代码来释放内存或资源。如果忘记了,系统可能会过载并崩溃。在 Rust 中,程序员可以指定每当值离开作用域时运行的代码,编译器会自动插入这段代码。
任何类型都可以实现 Drop trait,而 Drop 只要求实现 drop 方法,其参数是对 self 的可变引用。Drop 在预导入模块(prelude)中,因此使用时不必手动引入。看个例子:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created.");
}
- 结构体
CustomSmartPointer有一个data字段,类型为String。 - 通过
impl Drop for CustomSmartPointer为CustomSmartPointer实现了Droptrait。在其中实现drop方法,参数是&mut self。这个方法通常用于释放资源,但出于演示目的,这里只打印一句话,并把self中data字段的内容输出出来。 - 在
main中创建了两个CustomSmartPointer实例:c存的是"my stuff",d存的是"other stuff"。最后打印"CustomSmartPointers created."。
输出:
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
程序会先打印 main 中 println! 的内容,也就是 "CustomSmartPointers created."。因为 c 和 d 在 main 结束时离开作用域,所以程序会按声明的逆序丢弃它们:先 d,再 c。由于这里实现的 drop 会打印一句话,因此这两个值在被丢弃时都会各打印一行。
15.4.2 使用 std::mem::drop 来提前丢弃值
比较遗憾的是,我们很难直接禁用自动的 drop,也没必要这么做。因为 Drop trait 的目的就是自动处理清理逻辑。
此外,Rust 不允许手动调用 Drop trait 的 drop 方法。但你可以调用标准库函数 std::mem::drop 来提前丢弃一个值,这相当于提前调用了 Drop 的 drop 方法。它的参数就是要丢弃的值。看:
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
drop(c);
println!("CustomSmartPointers created.");
}
在 main 中,我们手动使用 drop 清理了 c,而 d 仍然是自动清理的。输出顺序应该是 c 在 d 之前。
输出:
Dropping CustomSmartPointer with data `my stuff`!
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
有些人可能会疑问:如果 c 在离开作用域之前就被丢弃了,那么离开作用域后编译器会不会再调用一次 drop,从而导致二次释放(double free)错误?答案是不会。Rust 的设计是安全的。它的所有权系统会保证引用有效,而 drop 也只会在确定不再使用这个值时被调用一次。