放置标记

上一节中的示例为 Rust 引入了一个有趣的问题。我们已经看到,可以有条件地初始化、反初始化和重新初始化内存位置,并且完全安全。对于 Copy 类型,这并不特别值得注意,因为它们只是一堆随机的位。然而,具有析构函数的类型则不同:每当变量被赋值或变量超出作用域时,Rust 都需要知道是否调用析构函数。如何通过条件初始化来做到这一点?

请注意,这不是所有赋值都需要担心的事情。特别是,通过解引用赋值会无条件地调用 Drop,而在 `let` 中赋值则无条件地不调用 Drop。

#![allow(unused)]
fn main() {
let mut x = Box::new(0); // let makes a fresh variable, so never need to drop
let y = &mut x;
*y = Box::new(1); // Deref assumes the referent is initialized, so always drops
}

只有在覆盖先前初始化的变量或其子字段之一时,才会出现此问题。

事实证明,Rust 实际上会在*运行时*跟踪是否应该释放某个类型。当变量被初始化和取消初始化时,该变量的*放置标记*会被切换。当变量可能需要被释放时,会评估此标记以确定是否应该释放它。

当然,在很多情况下,值在程序中的每个点的初始化状态都可以静态地知道。如果是这种情况,那么编译器理论上可以生成更高效的代码!例如,直线代码具有这样的*静态放置语义*

#![allow(unused)]
fn main() {
let mut x = Box::new(0); // x was uninit; just overwrite.
let mut y = x;           // y was uninit; just overwrite and make x uninit.
x = Box::new(0);         // x was uninit; just overwrite.
y = x;                   // y was init; Drop y, overwrite it, and make x uninit!
                         // y goes out of scope; y was init; Drop y!
                         // x goes out of scope; x was uninit; do nothing.
}

类似地,对于初始化而言,所有分支的行为都相同的代码也具有静态放置语义。

#![allow(unused)]
fn main() {
let condition = true;
let mut x = Box::new(0);    // x was uninit; just overwrite.
if condition {
    drop(x)                 // x gets moved out; make x uninit.
} else {
    println!("{}", x);
    drop(x)                 // x gets moved out; make x uninit.
}
x = Box::new(0);            // x was uninit; just overwrite.
                            // x goes out of scope; x was init; Drop x!
}

但是,像这样的代码*需要*运行时信息才能正确地调用 Drop。

#![allow(unused)]
fn main() {
let condition = true;
let x;
if condition {
    x = Box::new(0);        // x was uninit; just overwrite.
    println!("{}", x);
}
                            // x goes out of scope; x might be uninit;
                            // check the flag!
}

当然,在这种情况下,检索静态放置语义是微不足道的。

#![allow(unused)]
fn main() {
let condition = true;
if condition {
    let x = Box::new(0);
    println!("{}", x);
}
}

放置标记在堆栈上进行跟踪。在旧版本的 Rust 中,放置标记存储在实现 `Drop` 的类型的隐藏字段中。