Drop标志

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

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

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

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

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

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

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

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

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

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

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

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

if condition {
    let x = Box::new(0);
    println!("{}", x);
}

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

非安全方式:一个特殊情况是数组。安全Rust不允许部分地初始化数组,初始化一个数组时,你可以通过let x = [val; N]为每一个位置赋予相同的值,或者是单独指定每一个成员的值let x = [val1, val2, val3]。很多 ...