原文链接:https://doc.rust-lang.org/nomicon/drop-flags.html

Drop标志

前一章的例子涉及到Rust的一个有趣的问题。我们看到我们可以安全地为一段内存初始化、反初始化、再初始化。对于Copy类型,这一点不是很重要,因为数据不过是一堆字节而已。但是对于有析构函数的类型就是另外一回事了:变量每次被赋值或者离开作用域的时候,Rust都需要判断是否要调用析构函数。在有条件地初始化的情况下,Rust是如何做到这一点的呢?

注意,不是所有的赋值操作都需要考虑这一点。通过解引用赋值是一定会触发析构函数,而使用let赋值则一定不会触发:

  1. let mut x = Box::new(0); // let传建一个全新的变量,所以一定不会调用drop
  2. let y = &mut x;
  3. *y = Box::new(1); // 解引用假设被引用变量是初始化过的,所以一定会调用drop

只有当覆盖一个已经初始化的变量或者变量的一个子成员时,才需要考虑这个问题。

Rust实际上是在运行期判断是否销毁变量。当一个变量被初始化和反初始化时,变量会更新它的”drop标志“的状态。通过解析这个标志的值,判断变量是否真的需要执行drop。

当然,大多数情况下,在编译期就可以知道一个值在每一点的初始化状态。符合这一点的话,编译器理论上可以生成更有效率的代码!比如,无分支的程序有着如下的静态drop语义:

  1. let mut x = Box::new(0); // x未初始化;仅覆盖值
  2. let mut y = x; // y未初始化;仅覆盖值,并设置x为未初始化
  3. x = Box::new(0); // x未初始化;仅覆盖值
  4. y = x; // y已初始化;销毁y,覆盖它的值,设置x为未初始化
  5. // y离开作用域;y已初始化;销毁y
  6. // x离开作用域;x未初始化;什么都不用做

类似的,有分支的代码当所有分支中的初始化行为一致的时候,也可以有静态的drop语义:

  1. let mut x = Box::new(0); // x未初始化;仅覆盖值
  2. if condition {
  3. drop(x); // x失去值;设置x为未初始化
  4. } else {
  5. printn!("{}", x);
  6. drop(x); // x失去值;设置x为未初始化
  7. }
  8. x = Box::new(0); // x未初始化;仅覆盖值
  9. // x离开作用域;x已初始化;销毁x

但是,下面的代码则需要运行时信息以正确执行drop:

  1. let x;
  2. if condition {
  3. x = Box::new(0); // x未初始化;仅覆盖值
  4. println!("{}", x);
  5. }
  6. // x离开作用域;x可能未初始化
  7. // 检查drop标志

当然,修改为下面的代码就又可以得到静态drop语义:

  1. if condition {
  2. let x = Box::new(0);
  3. println!("{}", x);
  4. }

drop标志储存在栈中,并不在实现Drop的类型里。