在Rust中,同一作用域中的本地变量按照定义顺序的相反顺序进行丢弃。
fn test_foo(){
let s1:String = String::from("A");
let mut foo:Foo = Foo { data: & s1 };
let s2:String = String::from("B");
foo.data = & s2;
}
我们在此声明三个本地变量:依次为
s1
、
foo
和
s2
。Rust希望以相反的顺序将它们删除:
s2
先,然后是
foo
,最后是
s1
。但是,生命周期存在问题。一旦我们删除了
s2
,那么
foo.data
就是未初始化的,即它指向垃圾内存。
现在,没有Drop
为什么可以工作呢?Rust有一个叫做部分移动(partial moves)的概念。如果你有一个具有多个字段的结构体,在不使整个结构体无效的情况下,Rust允许你移出某些字段。原则上,如果我有一个
struct Person {
name: String,
age: i32,
occupation: String,
}
如果我执行了
let name = my_person.name;
(其中
my_person: Person
),那么我已经将值移出了一个
Person
。因此,
my_person.name
是无效的,并且在部分移动不存在的情况下,Rust 应该认为
my_person
是完全无效的。但是,我们知道
my_person.age
和
my_person.occupation
仍然有效,因此 Rust 将允许
age
和
occupation
保持原样。它记得
name
被移动了(因此是垃圾,不应该被丢弃),而
age
和
occupation
仍然有效。
在你的示例中,同样的事情正在发生。Rust 想要丢弃
s2
,但
Foo
仍然持有对它的引用。Rust 认为这没问题:我们将丢弃
s2
并简单地说
Foo
已经被部分移动:它的
data
字段不再有效。然后当我们下一步去丢弃
foo
时,我们不需要丢弃引用,只需要丢弃最外层的
Foo
层本身。
如果没有
Drop
实例,这是可以的,Rust 将允许它。但是,如果
impl<'a> Drop for Foo<'a>
在作用域内,则对于
Foo
,部分移动将被完全禁用。Rust 看到你正在实现一些自定义的
Drop
行为,现在它不会允许部分初始化的对象存在,因为我们必须丢弃部分初始化的对象,而 Rust 无法预测你的自定义
Drop
代码将要做什么或者它将要做出什么样的假设。
因此,使用
Drop
实现时,Rust 仍然想先丢弃
s2
,但它不能部分地移动引用出
foo
,因为那会使
foo
处于部分初始化状态,这是不允许的。