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]。很多 ...